4c6291e69b1b7c0620e9138383a8655ba1afb5e6
[MacVim.git] / src / MacVim / MMBackend.m
blob4c6291e69b1b7c0620e9138383a8655ba1afb5e6
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_abandon_macvim();
62 extern void im_preedit_changed_macvim(char *preedit_string, int cursor_index);
64 enum {
65     MMBlinkStateNone = 0,
66     MMBlinkStateOn,
67     MMBlinkStateOff
70 static NSString *MMSymlinkWarningString =
71     @"\n\n\tMost likely this is because you have symlinked directly to\n"
72      "\tthe Vim binary, which Cocoa does not allow.  Please use an\n"
73      "\talias or the mvim shell script instead.  If you have not used\n"
74      "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
77 // Keycodes recognized by Vim (struct taken from gui_x11.c and gui_w48.c)
78 // (The key codes were taken from Carbon/HIToolbox/Events.)
79 static struct specialkey
81     unsigned    key_sym;
82     char_u      vim_code0;
83     char_u      vim_code1;
84 } special_keys[] =
86     {0x7e /*kVK_UpArrow*/,       'k', 'u'},
87     {0x7d /*kVK_DownArrow*/,     'k', 'd'},
88     {0x7b /*kVK_LeftArrow*/,     'k', 'l'},
89     {0x7c /*kVK_RightArrow*/,    'k', 'r'},
91     {0x7a /*kVK_F1*/,            'k', '1'},
92     {0x78 /*kVK_F2*/,            'k', '2'},
93     {0x63 /*kVK_F3*/,            'k', '3'},
94     {0x76 /*kVK_F4*/,            'k', '4'},
95     {0x60 /*kVK_F5*/,            'k', '5'},
96     {0x61 /*kVK_F6*/,            'k', '6'},
97     {0x62 /*kVK_F7*/,            'k', '7'},
98     {0x64 /*kVK_F8*/,            'k', '8'},
99     {0x65 /*kVK_F9*/,            'k', '9'},
100     {0x6d /*kVK_F10*/,           'k', ';'},
102     {0x67 /*kVK_F11*/,           'F', '1'},
103     {0x6f /*kVK_F12*/,           'F', '2'},
104     {0x69 /*kVK_F13*/,           'F', '3'},
105     {0x6b /*kVK_F14*/,           'F', '4'},
106     {0x71 /*kVK_F15*/,           'F', '5'},
107     {0x6a /*kVK_F16*/,           'F', '6'},
108     {0x40 /*kVK_F17*/,           'F', '7'},
109     {0x4f /*kVK_F18*/,           'F', '8'},
110     {0x50 /*kVK_F19*/,           'F', '9'},
111     {0x5a /*kVK_F20*/,           'F', 'A'},
113     {0x72 /*kVK_Help*/,          '%', '1'},
114     {0x33 /*kVK_Delete*/,        'k', 'b'},
115     {0x75 /*kVK_ForwardDelete*/, 'k', 'D'},
116     {0x73 /*kVK_Home*/,          'k', 'h'},
117     {0x77 /*kVK_End*/,           '@', '7'},
118     {0x74 /*kVK_PageUp*/,        'k', 'P'},
119     {0x79 /*kVK_PageDown*/,      'k', 'N'},
121     /* Keypad keys: */
122     {0x45 /*kVK_ANSI_KeypadPlus*/,       'K', '6'},
123     {0x4e /*kVK_ANSI_KeypadMinus*/,      'K', '7'},
124     {0x4b /*kVK_ANSI_KeypadDivide*/,     'K', '8'},
125     {0x43 /*kVK_ANSI_KeypadMultiply*/,   'K', '9'},
126     {0x4c /*kVK_ANSI_KeypadEnter*/,      'K', 'A'},
127     {0x41 /*kVK_ANSI_KeypadDecimal*/,    'K', 'B'},
128     {0x47 /*kVK_ANSI_KeypadClear*/,      KS_EXTRA, (char_u)KE_KDEL},
130     {0x52 /*kVK_ANSI_Keypad0*/,  'K', 'C'},
131     {0x53 /*kVK_ANSI_Keypad1*/,  'K', 'D'},
132     {0x54 /*kVK_ANSI_Keypad2*/,  'K', 'E'},
133     {0x55 /*kVK_ANSI_Keypad3*/,  'K', 'F'},
134     {0x56 /*kVK_ANSI_Keypad4*/,  'K', 'G'},
135     {0x57 /*kVK_ANSI_Keypad5*/,  'K', 'H'},
136     {0x58 /*kVK_ANSI_Keypad6*/,  'K', 'I'},
137     {0x59 /*kVK_ANSI_Keypad7*/,  'K', 'J'},
138     {0x5b /*kVK_ANSI_Keypad8*/,  'K', 'K'},
139     {0x5c /*kVK_ANSI_Keypad9*/,  'K', 'L'},
141     /* Keys that we want to be able to use any modifier with: */
142     {0x31 /*kVK_Space*/,         ' ', NUL},
143     {0x30 /*kVK_Tab*/,           TAB, NUL},
144     {0x35 /*kVK_Escape*/,        ESC, NUL},
145     {0x24 /*kVK_Return*/,        CAR, NUL},
147     /* End of list marker: */
148     {0, 0, 0}
152 extern GuiFont gui_mch_retain_font(GuiFont font);
156 @interface NSString (MMServerNameCompare)
157 - (NSComparisonResult)serverNameCompare:(NSString *)string;
158 @end
163 @interface MMBackend (Private)
164 - (void)clearDrawData;
165 - (void)didChangeWholeLine;
166 - (void)waitForDialogReturn;
167 - (void)insertVimStateMessage;
168 - (void)processInputQueue;
169 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
170 - (void)doKeyDown:(NSString *)key
171           keyCode:(unsigned)code
172         modifiers:(int)mods;
173 - (BOOL)handleSpecialKey:(NSString *)key
174                  keyCode:(unsigned)code
175                modifiers:(int)mods;
176 - (BOOL)handleMacMetaKey:(int)ikey modifiers:(int)mods;
177 - (void)queueMessage:(int)msgid data:(NSData *)data;
178 - (void)connectionDidDie:(NSNotification *)notification;
179 - (void)blinkTimerFired:(NSTimer *)timer;
180 - (void)focusChange:(BOOL)on;
181 - (void)handleToggleToolbar;
182 - (void)handleScrollbarEvent:(NSData *)data;
183 - (void)handleSetFont:(NSData *)data;
184 - (void)handleDropFiles:(NSData *)data;
185 - (void)handleDropString:(NSData *)data;
186 - (void)startOdbEditWithArguments:(NSDictionary *)args;
187 - (void)handleXcodeMod:(NSData *)data;
188 - (void)handleOpenWithArguments:(NSDictionary *)args;
189 - (BOOL)checkForModifiedBuffers;
190 - (void)addInput:(NSString *)input;
191 - (BOOL)unusedEditor;
192 - (void)redrawScreen;
193 - (void)handleFindReplace:(NSDictionary *)args;
194 - (void)handleMarkedText:(NSData *)data;
195 @end
199 @interface MMBackend (ClientServer)
200 - (NSString *)connectionNameFromServerName:(NSString *)name;
201 - (NSConnection *)connectionForServerName:(NSString *)name;
202 - (NSConnection *)connectionForServerPort:(int)port;
203 - (void)serverConnectionDidDie:(NSNotification *)notification;
204 - (void)addClient:(NSDistantObject *)client;
205 - (NSString *)alternateServerNameForName:(NSString *)name;
206 @end
210 @implementation MMBackend
212 + (MMBackend *)sharedInstance
214     static MMBackend *singleton = nil;
215     return singleton ? singleton : (singleton = [MMBackend new]);
218 - (id)init
220     self = [super init];
221     if (!self) return nil;
223     outputQueue = [[NSMutableArray alloc] init];
224     inputQueue = [[NSMutableArray alloc] init];
225     drawData = [[NSMutableData alloc] initWithCapacity:1024];
226     connectionNameDict = [[NSMutableDictionary alloc] init];
227     clientProxyDict = [[NSMutableDictionary alloc] init];
228     serverReplyDict = [[NSMutableDictionary alloc] init];
230     NSBundle *mainBundle = [NSBundle mainBundle];
231     NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
232     if (path)
233         colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
235     path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
236     if (path)
237         sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
238             retain];
240     path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
241     if (path)
242         actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
244     if (!(colorDict && sysColorDict && actionDict)) {
245         ASLogNotice(@"Failed to load dictionaries.%@", MMSymlinkWarningString);
246     }
248     return self;
251 - (void)dealloc
253     ASLogDebug(@"");
255     [[NSNotificationCenter defaultCenter] removeObserver:self];
257     gui_mch_free_font(oldWideFont);  oldWideFont = NOFONT;
258     [blinkTimer release];  blinkTimer = nil;
259     [alternateServerName release];  alternateServerName = nil;
260     [serverReplyDict release];  serverReplyDict = nil;
261     [clientProxyDict release];  clientProxyDict = nil;
262     [connectionNameDict release];  connectionNameDict = nil;
263     [inputQueue release];  inputQueue = nil;
264     [outputQueue release];  outputQueue = nil;
265     [drawData release];  drawData = nil;
266     [connection release];  connection = nil;
267     [actionDict release];  actionDict = nil;
268     [sysColorDict release];  sysColorDict = nil;
269     [colorDict release];  colorDict = nil;
270     [vimServerConnection release];  vimServerConnection = nil;
272     [super dealloc];
275 - (void)setBackgroundColor:(int)color
277     backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
280 - (void)setForegroundColor:(int)color
282     foregroundColor = MM_COLOR(color);
285 - (void)setSpecialColor:(int)color
287     specialColor = MM_COLOR(color);
290 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
292     defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
293     defaultForegroundColor = MM_COLOR(fg);
295     NSMutableData *data = [NSMutableData data];
297     [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
298     [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
300     [self queueMessage:SetDefaultColorsMsgID data:data];
303 - (NSConnection *)connection
305     if (!connection) {
306         // NOTE!  If the name of the connection changes here it must also be
307         // updated in MMAppController.m.
308         NSString *name = [NSString stringWithFormat:@"%@-connection",
309                [[NSBundle mainBundle] bundlePath]];
311         connection = [NSConnection connectionWithRegisteredName:name host:nil];
312         [connection retain];
313     }
315     // NOTE: 'connection' may be nil here.
316     return connection;
319 - (NSDictionary *)actionDict
321     return actionDict;
324 - (int)initialWindowLayout
326     return initialWindowLayout;
329 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
331     [self queueMessage:msgid data:[props dictionaryAsData]];
334 - (BOOL)checkin
336     if (![self connection]) {
337         if (waitForAck) {
338             // This is a preloaded process and as such should not cause the
339             // MacVim to be opened.  We probably got here as a result of the
340             // user quitting MacVim while the process was preloading, so exit
341             // this process too.
342             // (Don't use mch_exit() since it assumes the process has properly
343             // started.)
344             exit(0);
345         }
347         NSBundle *mainBundle = [NSBundle mainBundle];
348 #if 0
349         OSStatus status;
350         FSRef ref;
352         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
353         // the API to pass Apple Event parameters is broken on 10.4).
354         NSString *path = [mainBundle bundlePath];
355         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
356         if (noErr == status) {
357             // Pass parameter to the 'Open' Apple Event that tells MacVim not
358             // to open an untitled window.
359             NSAppleEventDescriptor *desc =
360                     [NSAppleEventDescriptor recordDescriptor];
361             [desc setParamDescriptor:
362                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
363                           forKeyword:keyMMUntitledWindow];
365             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
366                     kLSLaunchDefaults, NULL };
367             status = LSOpenFromRefSpec(&spec, NULL);
368         }
370         if (noErr != status) {
371         ASLogCrit(@"Failed to launch MacVim (path=%@).%@",
372                   path, MMSymlinkWarningString);
373             return NO;
374         }
375 #else
376         // Launch MacVim using NSTask.  For some reason the above code using
377         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
378         // fails, the dock icon starts bouncing and never stops).  It seems
379         // like rebuilding the Launch Services database takes care of this
380         // problem, but the NSTask way seems more stable so stick with it.
381         //
382         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
383         // that the GUI won't be activated (or raised) so there is a hack in
384         // MMAppController which raises the app when a new window is opened.
385         NSMutableArray *args = [NSMutableArray arrayWithObjects:
386             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
387         NSString *exeName = [[mainBundle infoDictionary]
388                 objectForKey:@"CFBundleExecutable"];
389         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
390         if (!path) {
391             ASLogCrit(@"Could not find MacVim executable in bundle.%@",
392                       MMSymlinkWarningString);
393             return NO;
394         }
396         [NSTask launchedTaskWithLaunchPath:path arguments:args];
397 #endif
399         // HACK!  Poll the mach bootstrap server until it returns a valid
400         // connection to detect that MacVim has finished launching.  Also set a
401         // time-out date so that we don't get stuck doing this forever.
402         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
403         while (![self connection] &&
404                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
405             [[NSRunLoop currentRunLoop]
406                     runMode:NSDefaultRunLoopMode
407                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
409         // NOTE: [self connection] will set 'connection' as a side-effect.
410         if (!connection) {
411             ASLogCrit(@"Timed-out waiting for GUI to launch.");
412             return NO;
413         }
414     }
416     @try {
417         [[NSNotificationCenter defaultCenter] addObserver:self
418                 selector:@selector(connectionDidDie:)
419                     name:NSConnectionDidDieNotification object:connection];
421         appProxy = [[connection rootProxy] retain];
422         [appProxy setProtocolForProxy:@protocol(MMAppProtocol)];
424         // NOTE: We do not set any new timeout values for the connection to the
425         // frontend.  This means that if the frontend is "stuck" (e.g. in a
426         // modal loop) then any calls to the frontend will block indefinitely
427         // (the default timeouts are huge).
429         int pid = [[NSProcessInfo processInfo] processIdentifier];
431         identifier = [appProxy connectBackend:self pid:pid];
432         return YES;
433     }
434     @catch (NSException *ex) {
435         ASLogDebug(@"Connect backend failed: reason=%@", ex);
436     }
438     return NO;
441 - (BOOL)openGUIWindow
443     [self queueMessage:OpenWindowMsgID data:nil];
444     return YES;
447 - (void)clearAll
449     int type = ClearAllDrawType;
451     // Any draw commands in queue are effectively obsolete since this clearAll
452     // will negate any effect they have, therefore we may as well clear the
453     // draw queue.
454     [self clearDrawData];
456     [drawData appendBytes:&type length:sizeof(int)];
459 - (void)clearBlockFromRow:(int)row1 column:(int)col1
460                     toRow:(int)row2 column:(int)col2
462     int type = ClearBlockDrawType;
464     [drawData appendBytes:&type length:sizeof(int)];
466     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
467     [drawData appendBytes:&row1 length:sizeof(int)];
468     [drawData appendBytes:&col1 length:sizeof(int)];
469     [drawData appendBytes:&row2 length:sizeof(int)];
470     [drawData appendBytes:&col2 length:sizeof(int)];
473 - (void)deleteLinesFromRow:(int)row count:(int)count
474               scrollBottom:(int)bottom left:(int)left right:(int)right
476     int type = DeleteLinesDrawType;
478     [drawData appendBytes:&type length:sizeof(int)];
480     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
481     [drawData appendBytes:&row length:sizeof(int)];
482     [drawData appendBytes:&count length:sizeof(int)];
483     [drawData appendBytes:&bottom length:sizeof(int)];
484     [drawData appendBytes:&left length:sizeof(int)];
485     [drawData appendBytes:&right length:sizeof(int)];
487     if (left == 0 && right == gui.num_cols-1)
488         [self didChangeWholeLine];
491 - (void)drawString:(char_u*)s length:(int)len row:(int)row
492             column:(int)col cells:(int)cells flags:(int)flags
494     if (len <= 0) return;
496     int type = DrawStringDrawType;
498     [drawData appendBytes:&type length:sizeof(int)];
500     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
501     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
502     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
503     [drawData appendBytes:&row length:sizeof(int)];
504     [drawData appendBytes:&col length:sizeof(int)];
505     [drawData appendBytes:&cells length:sizeof(int)];
506     [drawData appendBytes:&flags length:sizeof(int)];
507     [drawData appendBytes:&len length:sizeof(int)];
508     [drawData appendBytes:s length:len];
511 - (void)insertLinesFromRow:(int)row count:(int)count
512               scrollBottom:(int)bottom left:(int)left right:(int)right
514     int type = InsertLinesDrawType;
516     [drawData appendBytes:&type length:sizeof(int)];
518     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
519     [drawData appendBytes:&row length:sizeof(int)];
520     [drawData appendBytes:&count length:sizeof(int)];
521     [drawData appendBytes:&bottom length:sizeof(int)];
522     [drawData appendBytes:&left length:sizeof(int)];
523     [drawData appendBytes:&right length:sizeof(int)];
525     if (left == 0 && right == gui.num_cols-1)
526         [self didChangeWholeLine];
529 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
530                fraction:(int)percent color:(int)color
532     int type = DrawCursorDrawType;
533     unsigned uc = MM_COLOR(color);
535     [drawData appendBytes:&type length:sizeof(int)];
537     [drawData appendBytes:&uc length:sizeof(unsigned)];
538     [drawData appendBytes:&row length:sizeof(int)];
539     [drawData appendBytes:&col length:sizeof(int)];
540     [drawData appendBytes:&shape length:sizeof(int)];
541     [drawData appendBytes:&percent length:sizeof(int)];
544 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
545                    numColumns:(int)nc invert:(int)invert
547     int type = DrawInvertedRectDrawType;
548     [drawData appendBytes:&type length:sizeof(int)];
550     [drawData appendBytes:&row length:sizeof(int)];
551     [drawData appendBytes:&col length:sizeof(int)];
552     [drawData appendBytes:&nr length:sizeof(int)];
553     [drawData appendBytes:&nc length:sizeof(int)];
554     [drawData appendBytes:&invert length:sizeof(int)];
557 - (void)update
559     // Keep running the run-loop until there is no more input to process.
560     while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
561             == kCFRunLoopRunHandledSource)
562         ;   // do nothing
565 - (void)flushQueue:(BOOL)force
567     // NOTE: This variable allows for better control over when the queue is
568     // flushed.  It can be set to YES at the beginning of a sequence of calls
569     // that may potentially add items to the queue, and then restored back to
570     // NO.
571     if (flushDisabled) return;
573     if ([drawData length] > 0) {
574         // HACK!  Detect changes to 'guifontwide'.
575         if (gui.wide_font != oldWideFont) {
576             gui_mch_free_font(oldWideFont);
577             oldWideFont = gui_mch_retain_font(gui.wide_font);
578             [self setFont:oldWideFont wide:YES];
579         }
581         int type = SetCursorPosDrawType;
582         [drawData appendBytes:&type length:sizeof(type)];
583         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
584         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
586         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
587         [self clearDrawData];
588     }
590     if ([outputQueue count] > 0) {
591         [self insertVimStateMessage];
593         @try {
594             ASLogDebug(@"Flushing queue: %@",
595                        debugStringForMessageQueue(outputQueue));
596             [appProxy processInput:outputQueue forIdentifier:identifier];
597         }
598         @catch (NSException *ex) {
599             ASLogDebug(@"processInput:forIdentifer failed: reason=%@", ex);
600             if (![connection isValid]) {
601                 ASLogDebug(@"Connection is invalid, exit now!");
602                 ASLogDebug(@"waitForAck=%d got_int=%d", waitForAck, got_int);
603                 mch_exit(-1);
604             }
605         }
607         [outputQueue removeAllObjects];
608     }
611 - (BOOL)waitForInput:(int)milliseconds
613     // Return NO if we timed out waiting for input, otherwise return YES.
614     BOOL inputReceived = NO;
616     // Only start the run loop if the input queue is empty, otherwise process
617     // the input first so that the input on queue isn't delayed.
618     if ([inputQueue count]) {
619         inputReceived = YES;
620     } else {
621         // Wait for the specified amount of time, unless 'milliseconds' is
622         // negative in which case we wait "forever" (1e6 seconds translates to
623         // approximately 11 days).
624         CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
626         while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
627                 == kCFRunLoopRunHandledSource) {
628             // In order to ensure that all input on the run-loop has been
629             // processed we set the timeout to 0 and keep processing until the
630             // run-loop times out.
631             dt = 0.0;
632             inputReceived = YES;
633         }
634     }
636     // The above calls may have placed messages on the input queue so process
637     // it now.  This call may enter a blocking loop.
638     if ([inputQueue count] > 0)
639         [self processInputQueue];
641     return inputReceived;
644 - (void)exit
646     // NOTE: This is called if mch_exit() is called.  Since we assume here that
647     // the process has started properly, be sure to use exit() instead of
648     // mch_exit() to prematurely terminate a process (or set 'isTerminating'
649     // first).
651     // Make sure no connectionDidDie: notification is received now that we are
652     // already exiting.
653     [[NSNotificationCenter defaultCenter] removeObserver:self];
655     // The 'isTerminating' flag indicates that the frontend is also exiting so
656     // there is no need to flush any more output since the frontend won't look
657     // at it anyway.
658     if (!isTerminating && [connection isValid]) {
659         @try {
660             // Flush the entire queue in case a VimLeave autocommand added
661             // something to the queue.
662             [self queueMessage:CloseWindowMsgID data:nil];
663             ASLogDebug(@"Flush output queue before exit: %@",
664                        debugStringForMessageQueue(outputQueue));
665             [appProxy processInput:outputQueue forIdentifier:identifier];
666         }
667         @catch (NSException *ex) {
668             ASLogDebug(@"CloseWindowMsgID send failed: reason=%@", ex);
669         }
671         // NOTE: If Cmd-w was pressed to close the window the menu is briefly
672         // highlighted and during this pause the frontend won't receive any DO
673         // messages.  If the Vim process exits before this highlighting has
674         // finished Cocoa will emit the following error message:
675         //   *** -[NSMachPort handlePortMessage:]: dropping incoming DO message
676         //   because the connection or ports are invalid
677         // To avoid this warning we delay here.  If the warning still appears
678         // this delay may need to be increased.
679         usleep(150000);
680     }
682 #ifdef MAC_CLIENTSERVER
683     // The default connection is used for the client/server code.
684     if (vimServerConnection) {
685         [vimServerConnection setRootObject:nil];
686         [vimServerConnection invalidate];
687     }
688 #endif
691 - (void)selectTab:(int)index
693     index -= 1;
694     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
695     [self queueMessage:SelectTabMsgID data:data];
698 - (void)updateTabBar
700     NSMutableData *data = [NSMutableData data];
702     int idx = tabpage_index(curtab) - 1;
703     [data appendBytes:&idx length:sizeof(int)];
705     tabpage_T *tp;
706     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
707         // Count the number of windows in the tabpage.
708         //win_T *wp = tp->tp_firstwin;
709         //int wincount;
710         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
711         //[data appendBytes:&wincount length:sizeof(int)];
713         int tabProp = MMTabInfoCount;
714         [data appendBytes:&tabProp length:sizeof(int)];
715         for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
716             // This function puts the label of the tab in the global 'NameBuff'.
717             get_tabline_label(tp, (tabProp == MMTabToolTip));
718             NSString *s = [NSString stringWithVimString:NameBuff];
719             int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
720             if (len < 0)
721                 len = 0;
723             [data appendBytes:&len length:sizeof(int)];
724             if (len > 0)
725                 [data appendBytes:[s UTF8String] length:len];
726         }
727     }
729     [self queueMessage:UpdateTabBarMsgID data:data];
732 - (BOOL)tabBarVisible
734     return tabBarVisible;
737 - (void)showTabBar:(BOOL)enable
739     tabBarVisible = enable;
741     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
742     [self queueMessage:msgid data:nil];
745 - (void)setRows:(int)rows columns:(int)cols
747     int dim[] = { rows, cols };
748     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
750     [self queueMessage:SetTextDimensionsMsgID data:data];
753 - (void)setWindowTitle:(char *)title
755     NSMutableData *data = [NSMutableData data];
756     int len = strlen(title);
757     if (len <= 0) return;
759     [data appendBytes:&len length:sizeof(int)];
760     [data appendBytes:title length:len];
762     [self queueMessage:SetWindowTitleMsgID data:data];
765 - (void)setDocumentFilename:(char *)filename
767     NSMutableData *data = [NSMutableData data];
768     int len = filename ? strlen(filename) : 0;
770     [data appendBytes:&len length:sizeof(int)];
771     if (len > 0)
772         [data appendBytes:filename length:len];
774     [self queueMessage:SetDocumentFilenameMsgID data:data];
777 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
779     char_u *s = NULL;
781     [self queueMessage:BrowseForFileMsgID properties:attr];
782     [self flushQueue:YES];
784     @try {
785         [self waitForDialogReturn];
787         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
788             s = [dialogReturn vimStringSave];
790         [dialogReturn release];  dialogReturn = nil;
791     }
792     @catch (NSException *ex) {
793         ASLogDebug(@"Exception: reason=%@", ex);
794     }
796     return (char *)s;
799 - (oneway void)setDialogReturn:(in bycopy id)obj
801     ASLogDebug(@"%@", obj);
803     // NOTE: This is called by
804     //   - [MMVimController panelDidEnd:::], and
805     //   - [MMVimController alertDidEnd:::],
806     // to indicate that a save/open panel or alert has finished.
808     // We want to distinguish between "no dialog return yet" and "dialog
809     // returned nothing".  The former can be tested with dialogReturn == nil,
810     // the latter with dialogReturn == [NSNull null].
811     if (!obj) obj = [NSNull null];
813     if (obj != dialogReturn) {
814         [dialogReturn release];
815         dialogReturn = [obj retain];
816     }
819 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
821     int retval = 0;
823     [self queueMessage:ShowDialogMsgID properties:attr];
824     [self flushQueue:YES];
826     @try {
827         [self waitForDialogReturn];
829         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
830                 && [dialogReturn count]) {
831             retval = [[dialogReturn objectAtIndex:0] intValue];
832             if (txtfield && [dialogReturn count] > 1) {
833                 NSString *retString = [dialogReturn objectAtIndex:1];
834                 char_u *ret = (char_u*)[retString UTF8String];
835 #ifdef FEAT_MBYTE
836                 ret = CONVERT_FROM_UTF8(ret);
837 #endif
838                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
839 #ifdef FEAT_MBYTE
840                 CONVERT_FROM_UTF8_FREE(ret);
841 #endif
842             }
843         }
845         [dialogReturn release]; dialogReturn = nil;
846     }
847     @catch (NSException *ex) {
848         ASLogDebug(@"Exception: reason=%@", ex);
849     }
851     return retval;
854 - (void)showToolbar:(int)enable flags:(int)flags
856     NSMutableData *data = [NSMutableData data];
858     [data appendBytes:&enable length:sizeof(int)];
859     [data appendBytes:&flags length:sizeof(int)];
861     [self queueMessage:ShowToolbarMsgID data:data];
864 - (void)createScrollbarWithIdentifier:(int32_t)ident type:(int)type
866     NSMutableData *data = [NSMutableData data];
868     [data appendBytes:&ident length:sizeof(int32_t)];
869     [data appendBytes:&type length:sizeof(int)];
871     [self queueMessage:CreateScrollbarMsgID data:data];
874 - (void)destroyScrollbarWithIdentifier:(int32_t)ident
876     NSMutableData *data = [NSMutableData data];
877     [data appendBytes:&ident length:sizeof(int32_t)];
879     [self queueMessage:DestroyScrollbarMsgID data:data];
882 - (void)showScrollbarWithIdentifier:(int32_t)ident state:(int)visible
884     NSMutableData *data = [NSMutableData data];
886     [data appendBytes:&ident length:sizeof(int32_t)];
887     [data appendBytes:&visible length:sizeof(int)];
889     [self queueMessage:ShowScrollbarMsgID data:data];
892 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(int32_t)ident
894     NSMutableData *data = [NSMutableData data];
896     [data appendBytes:&ident length:sizeof(int32_t)];
897     [data appendBytes:&pos length:sizeof(int)];
898     [data appendBytes:&len length:sizeof(int)];
900     [self queueMessage:SetScrollbarPositionMsgID data:data];
903 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
904                     identifier:(int32_t)ident
906     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
907     float prop = (float)size/(max+1);
908     if (fval < 0) fval = 0;
909     else if (fval > 1.0f) fval = 1.0f;
910     if (prop < 0) prop = 0;
911     else if (prop > 1.0f) prop = 1.0f;
913     NSMutableData *data = [NSMutableData data];
915     [data appendBytes:&ident length:sizeof(int32_t)];
916     [data appendBytes:&fval length:sizeof(float)];
917     [data appendBytes:&prop length:sizeof(float)];
919     [self queueMessage:SetScrollbarThumbMsgID data:data];
922 - (void)setFont:(GuiFont)font wide:(BOOL)wide
924     NSString *fontName = (NSString *)font;
925     float size = 0;
926     NSArray *components = [fontName componentsSeparatedByString:@":"];
927     if ([components count] == 2) {
928         size = [[components lastObject] floatValue];
929         fontName = [components objectAtIndex:0];
930     }
932     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
933     NSMutableData *data = [NSMutableData data];
934     [data appendBytes:&size length:sizeof(float)];
935     [data appendBytes:&len length:sizeof(int)];
937     if (len > 0)
938         [data appendBytes:[fontName UTF8String] length:len];
939     else if (!wide)
940         return;     // Only the wide font can be set to nothing
942     [self queueMessage:(wide ? SetWideFontMsgID : SetFontMsgID) data:data];
945 - (void)executeActionWithName:(NSString *)name
947     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
949     if (len > 0) {
950         NSMutableData *data = [NSMutableData data];
952         [data appendBytes:&len length:sizeof(int)];
953         [data appendBytes:[name UTF8String] length:len];
955         [self queueMessage:ExecuteActionMsgID data:data];
956     }
959 - (void)setMouseShape:(int)shape
961     NSMutableData *data = [NSMutableData data];
962     [data appendBytes:&shape length:sizeof(int)];
963     [self queueMessage:SetMouseShapeMsgID data:data];
966 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
968     // Vim specifies times in milliseconds, whereas Cocoa wants them in
969     // seconds.
970     blinkWaitInterval = .001f*wait;
971     blinkOnInterval = .001f*on;
972     blinkOffInterval = .001f*off;
975 - (void)startBlink
977     if (blinkTimer) {
978         [blinkTimer invalidate];
979         [blinkTimer release];
980         blinkTimer = nil;
981     }
983     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
984             && gui.in_focus) {
985         blinkState = MMBlinkStateOn;
986         blinkTimer =
987             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
988                                               target:self
989                                             selector:@selector(blinkTimerFired:)
990                                             userInfo:nil repeats:NO] retain];
991         gui_update_cursor(TRUE, FALSE);
992         [self flushQueue:YES];
993     }
996 - (void)stopBlink
998     if (MMBlinkStateOff == blinkState) {
999         gui_update_cursor(TRUE, FALSE);
1000         [self flushQueue:YES];
1001     }
1003     blinkState = MMBlinkStateNone;
1006 - (void)adjustLinespace:(int)linespace
1008     NSMutableData *data = [NSMutableData data];
1009     [data appendBytes:&linespace length:sizeof(int)];
1010     [self queueMessage:AdjustLinespaceMsgID data:data];
1013 - (void)activate
1015     [self queueMessage:ActivateMsgID data:nil];
1018 - (void)setPreEditRow:(int)row column:(int)col
1020     NSMutableData *data = [NSMutableData data];
1021     [data appendBytes:&row length:sizeof(int)];
1022     [data appendBytes:&col length:sizeof(int)];
1023     [self queueMessage:SetPreEditPositionMsgID data:data];
1026 - (int)lookupColorWithKey:(NSString *)key
1028     if (!(key && [key length] > 0))
1029         return INVALCOLOR;
1031     NSString *stripKey = [[[[key lowercaseString]
1032         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
1033             componentsSeparatedByString:@" "]
1034                componentsJoinedByString:@""];
1036     if (stripKey && [stripKey length] > 0) {
1037         // First of all try to lookup key in the color dictionary; note that
1038         // all keys in this dictionary are lowercase with no whitespace.
1039         id obj = [colorDict objectForKey:stripKey];
1040         if (obj) return [obj intValue];
1042         // The key was not in the dictionary; is it perhaps of the form
1043         // #rrggbb?
1044         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
1045             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
1046             [scanner setScanLocation:1];
1047             unsigned hex = 0;
1048             if ([scanner scanHexInt:&hex]) {
1049                 return (int)hex;
1050             }
1051         }
1053         // As a last resort, check if it is one of the system defined colors.
1054         // The keys in this dictionary are also lowercase with no whitespace.
1055         obj = [sysColorDict objectForKey:stripKey];
1056         if (obj) {
1057             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
1058             if (col) {
1059                 CGFloat r, g, b, a;
1060                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
1061                 [col getRed:&r green:&g blue:&b alpha:&a];
1062                 return (((int)(r*255+.5f) & 0xff) << 16)
1063                      + (((int)(g*255+.5f) & 0xff) << 8)
1064                      +  ((int)(b*255+.5f) & 0xff);
1065             }
1066         }
1067     }
1069     ASLogNotice(@"No color with key %@ found.", stripKey);
1070     return INVALCOLOR;
1073 - (BOOL)hasSpecialKeyWithValue:(char_u *)value
1075     int i;
1076     for (i = 0; special_keys[i].key_sym != 0; i++) {
1077         if (value[0] == special_keys[i].vim_code0
1078                 && value[1] == special_keys[i].vim_code1)
1079             return YES;
1080     }
1082     return NO;
1085 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1087     NSMutableData *data = [NSMutableData data];
1088     [data appendBytes:&fuoptions length:sizeof(int)];
1089     bg = MM_COLOR(bg);
1090     [data appendBytes:&bg length:sizeof(int)];
1091     [self queueMessage:EnterFullscreenMsgID data:data];
1094 - (void)leaveFullscreen
1096     [self queueMessage:LeaveFullscreenMsgID data:nil];
1099 - (void)setFullscreenBackgroundColor:(int)color
1101     NSMutableData *data = [NSMutableData data];
1102     color = MM_COLOR(color);
1103     [data appendBytes:&color length:sizeof(int)];
1105     [self queueMessage:SetFullscreenColorMsgID data:data];
1108 - (void)setAntialias:(BOOL)antialias
1110     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1112     [self queueMessage:msgid data:nil];
1115 - (void)updateModifiedFlag
1117     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1118     // vice versa.
1119     int msgid = [self checkForModifiedBuffers]
1120             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1122     [self queueMessage:msgid data:nil];
1125 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1127     // Look for Ctrl-C immediately instead of waiting until the input queue is
1128     // processed since that only happens in waitForInput: (and Vim regularly
1129     // checks for Ctrl-C in between waiting for input).  Note that the flag
1130     // ctrl_c_interrupts is 0 e.g. when the user has mappings to something like
1131     // <C-c>g.  Also it seems the flag intr_char is 0 when MacVim was started
1132     // from Finder whereas it is 0x03 (= Ctrl_C) when started from Terminal.
1133     //
1134     // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1135     // which waits on the run loop will fail to detect this message (e.g. in
1136     // waitForConnectionAcknowledgement).
1138     if (KeyDownMsgID == msgid && data != nil && ctrl_c_interrupts) {
1139         const void *bytes = [data bytes];
1140         /*unsigned mods = *((unsigned*)bytes);*/  bytes += sizeof(unsigned);
1141         /*unsigned code = *((unsigned*)bytes);*/  bytes += sizeof(unsigned);
1142         unsigned len  = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1143         if (1 == len) {
1144             char_u *str = (char_u*)bytes;
1145             if (str[0] == Ctrl_C || (str[0] == intr_char && intr_char != 0)) {
1146                 ASLogDebug(@"Got INT, str[0]=%#x ctrl_c_interrupts=%d "
1147                         "intr_char=%#x", str[0], ctrl_c_interrupts, intr_char);
1148                 got_int = TRUE;
1149                 [inputQueue removeAllObjects];
1150                 return;
1151             }
1152         }
1153     } else if (TerminateNowMsgID == msgid) {
1154         // Terminate immediately (the frontend is about to quit or this process
1155         // was aborted).  Don't preserve modified files since the user would
1156         // already have been presented with a dialog warning if there were any
1157         // modified files when we get here.
1158         isTerminating = YES;
1159         getout(0);
1160         return;
1161     }
1163     // Remove all previous instances of this message from the input queue, else
1164     // the input queue may fill up as a result of Vim not being able to keep up
1165     // with the speed at which new messages are received.
1166     // Keyboard input is never dropped, unless the input represents an
1167     // auto-repeated key.
1169     BOOL isKeyRepeat = NO;
1170     BOOL isKeyboardInput = NO;
1172     if (data && KeyDownMsgID == msgid) {
1173         isKeyboardInput = YES;
1175         // The lowest bit of the first int is set if this key is a repeat.
1176         int flags = *((int*)[data bytes]);
1177         if (flags & 1)
1178             isKeyRepeat = YES;
1179     }
1181     // Keyboard input is not removed from the queue; repeats are ignored if
1182     // there already is keyboard input on the input queue.
1183     if (isKeyRepeat || !isKeyboardInput) {
1184         int i, count = [inputQueue count];
1185         for (i = 1; i < count; i+=2) {
1186             if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1187                 if (isKeyRepeat)
1188                     return;
1190                 [inputQueue removeObjectAtIndex:i];
1191                 [inputQueue removeObjectAtIndex:i-1];
1192                 break;
1193             }
1194         }
1195     }
1197     [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1198     [inputQueue addObject:(data ? (id)data : [NSNull null])];
1201 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1202                   errorString:(out bycopy NSString **)errstr
1204     return evalExprCocoa(expr, errstr);
1208 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1210     NSString *eval = nil;
1211     char_u *s = (char_u*)[expr UTF8String];
1213 #ifdef FEAT_MBYTE
1214     s = CONVERT_FROM_UTF8(s);
1215 #endif
1217     char_u *res = eval_client_expr_to_string(s);
1219 #ifdef FEAT_MBYTE
1220     CONVERT_FROM_UTF8_FREE(s);
1221 #endif
1223     if (res != NULL) {
1224         s = res;
1225 #ifdef FEAT_MBYTE
1226         s = CONVERT_TO_UTF8(s);
1227 #endif
1228         eval = [NSString stringWithUTF8String:(char*)s];
1229 #ifdef FEAT_MBYTE
1230         CONVERT_TO_UTF8_FREE(s);
1231 #endif
1232         vim_free(res);
1233     }
1235     return eval;
1238 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1240     // TODO: This method should share code with clip_mch_request_selection().
1242     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1243         // If there is no pasteboard, return YES to indicate that there is text
1244         // to copy.
1245         if (!pboard)
1246             return YES;
1248         clip_copy_selection();
1250         // Get the text to put on the pasteboard.
1251         long_u llen = 0; char_u *str = 0;
1252         int type = clip_convert_selection(&str, &llen, &clip_star);
1253         if (type < 0)
1254             return NO;
1255         
1256         // TODO: Avoid overflow.
1257         int len = (int)llen;
1258 #ifdef FEAT_MBYTE
1259         if (output_conv.vc_type != CONV_NONE) {
1260             char_u *conv_str = string_convert(&output_conv, str, &len);
1261             if (conv_str) {
1262                 vim_free(str);
1263                 str = conv_str;
1264             }
1265         }
1266 #endif
1268         NSString *string = [[NSString alloc]
1269             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1271         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1272         [pboard declareTypes:types owner:nil];
1273         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1274     
1275         [string release];
1276         vim_free(str);
1278         return ok;
1279     }
1281     return NO;
1284 - (oneway void)addReply:(in bycopy NSString *)reply
1285                  server:(in byref id <MMVimServerProtocol>)server
1287     ASLogDebug(@"reply=%@ server=%@", reply, (id)server);
1289     // Replies might come at any time and in any order so we keep them in an
1290     // array inside a dictionary with the send port used as key.
1292     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1293     // HACK! Assume connection uses mach ports.
1294     int port = [(NSMachPort*)[conn sendPort] machPort];
1295     NSNumber *key = [NSNumber numberWithInt:port];
1297     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1298     if (!replies) {
1299         replies = [NSMutableArray array];
1300         [serverReplyDict setObject:replies forKey:key];
1301     }
1303     [replies addObject:reply];
1306 - (void)addInput:(in bycopy NSString *)input
1307           client:(in byref id <MMVimClientProtocol>)client
1309     ASLogDebug(@"input=%@ client=%@", input, (id)client);
1311     // NOTE: We don't call addInput: here because it differs from
1312     // server_to_input_buf() in that it always sets the 'silent' flag and we
1313     // don't want the MacVim client/server code to behave differently from
1314     // other platforms.
1315     char_u *s = [input vimStringSave];
1316     server_to_input_buf(s);
1317     vim_free(s);
1319     [self addClient:(id)client];
1322 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1323                  client:(in byref id <MMVimClientProtocol>)client
1325     [self addClient:(id)client];
1326     return [self evaluateExpression:expr];
1329 - (void)registerServerWithName:(NSString *)name
1331     NSString *svrName = name;
1332     unsigned i;
1334     if (vimServerConnection) // Paranoia check, should always be nil
1335         [vimServerConnection release];
1337     vimServerConnection = [[NSConnection alloc]
1338                                             initWithReceivePort:[NSPort port]
1339                                                        sendPort:nil];
1341     for (i = 0; i < MMServerMax; ++i) {
1342         NSString *connName = [self connectionNameFromServerName:svrName];
1344         if ([vimServerConnection registerName:connName]) {
1345             ASLogInfo(@"Registered server with name: %@", svrName);
1347             // TODO: Set request/reply time-outs to something else?
1348             //
1349             // Don't wait for requests (time-out means that the message is
1350             // dropped).
1351             [vimServerConnection setRequestTimeout:0];
1352             //[vimServerConnection setReplyTimeout:MMReplyTimeout];
1353             [vimServerConnection setRootObject:self];
1355             // NOTE: 'serverName' is a global variable
1356             serverName = [svrName vimStringSave];
1357 #ifdef FEAT_EVAL
1358             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1359 #endif
1360 #ifdef FEAT_TITLE
1361             need_maketitle = TRUE;
1362 #endif
1363             [self queueMessage:SetServerNameMsgID
1364                         data:[svrName dataUsingEncoding:NSUTF8StringEncoding]];
1365             break;
1366         }
1368         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1369     }
1372 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1373                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1374               silent:(BOOL)silent
1376     // NOTE: If 'name' equals 'serverName' then the request is local (client
1377     // and server are the same).  This case is not handled separately, so a
1378     // connection will be set up anyway (this simplifies the code).
1380     NSConnection *conn = [self connectionForServerName:name];
1381     if (!conn) {
1382         if (!silent) {
1383             char_u *s = (char_u*)[name UTF8String];
1384 #ifdef FEAT_MBYTE
1385             s = CONVERT_FROM_UTF8(s);
1386 #endif
1387             EMSG2(_(e_noserver), s);
1388 #ifdef FEAT_MBYTE
1389             CONVERT_FROM_UTF8_FREE(s);
1390 #endif
1391         }
1392         return NO;
1393     }
1395     if (port) {
1396         // HACK! Assume connection uses mach ports.
1397         *port = [(NSMachPort*)[conn sendPort] machPort];
1398     }
1400     id proxy = [conn rootProxy];
1401     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1403     @try {
1404         if (expr) {
1405             NSString *eval = [proxy evaluateExpression:string client:self];
1406             if (reply) {
1407                 if (eval) {
1408                     *reply = [eval vimStringSave];
1409                 } else {
1410                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1411                 }
1412             }
1414             if (!eval)
1415                 return NO;
1416         } else {
1417             [proxy addInput:string client:self];
1418         }
1419     }
1420     @catch (NSException *ex) {
1421         ASLogDebug(@"Exception: reason=%@", ex);
1422         return NO;
1423     }
1425     return YES;
1428 - (NSArray *)serverList
1430     NSArray *list = nil;
1432     if ([self connection]) {
1433         id proxy = [connection rootProxy];
1434         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1436         @try {
1437             list = [proxy serverList];
1438         }
1439         @catch (NSException *ex) {
1440             ASLogDebug(@"serverList failed: reason=%@", ex);
1441         }
1442     } else {
1443         // We get here if a --remote flag is used before MacVim has started.
1444         ASLogInfo(@"No connection to MacVim, server listing not possible.");
1445     }
1447     return list;
1450 - (NSString *)peekForReplyOnPort:(int)port
1452     ASLogDebug(@"port=%d", port);
1454     NSNumber *key = [NSNumber numberWithInt:port];
1455     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1456     if (replies && [replies count]) {
1457         ASLogDebug(@"    %d replies, topmost is: %@", [replies count],
1458                    [replies objectAtIndex:0]);
1459         return [replies objectAtIndex:0];
1460     }
1462     ASLogDebug(@"    No replies");
1463     return nil;
1466 - (NSString *)waitForReplyOnPort:(int)port
1468     ASLogDebug(@"port=%d", port);
1469     
1470     NSConnection *conn = [self connectionForServerPort:port];
1471     if (!conn)
1472         return nil;
1474     NSNumber *key = [NSNumber numberWithInt:port];
1475     NSMutableArray *replies = nil;
1476     NSString *reply = nil;
1478     // Wait for reply as long as the connection to the server is valid (unless
1479     // user interrupts wait with Ctrl-C).
1480     while (!got_int && [conn isValid] &&
1481             !(replies = [serverReplyDict objectForKey:key])) {
1482         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1483                                  beforeDate:[NSDate distantFuture]];
1484     }
1486     if (replies) {
1487         if ([replies count] > 0) {
1488             reply = [[replies objectAtIndex:0] retain];
1489             ASLogDebug(@"    Got reply: %@", reply);
1490             [replies removeObjectAtIndex:0];
1491             [reply autorelease];
1492         }
1494         if ([replies count] == 0)
1495             [serverReplyDict removeObjectForKey:key];
1496     }
1498     return reply;
1501 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1503     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1504     if (client) {
1505         @try {
1506             ASLogDebug(@"reply=%@ port=%d", reply, port);
1507             [client addReply:reply server:self];
1508             return YES;
1509         }
1510         @catch (NSException *ex) {
1511             ASLogDebug(@"addReply:server: failed: reason=%@", ex);
1512         }
1513     } else {
1514         ASLogNotice(@"server2client failed; no client with id %d", port);
1515     }
1517     return NO;
1520 - (BOOL)waitForAck
1522     return waitForAck;
1525 - (void)setWaitForAck:(BOOL)yn
1527     waitForAck = yn;
1530 - (void)waitForConnectionAcknowledgement
1532     if (!waitForAck) return;
1534     while (waitForAck && !got_int && [connection isValid]) {
1535         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1536                                  beforeDate:[NSDate distantFuture]];
1537         ASLogDebug(@"  waitForAck=%d got_int=%d isValid=%d",
1538                    waitForAck, got_int, [connection isValid]);
1539     }
1541     if (waitForAck) {
1542         ASLogDebug(@"Never received a connection acknowledgement");
1543         [[NSNotificationCenter defaultCenter] removeObserver:self];
1544         [appProxy release];  appProxy = nil;
1546         // NOTE: We intentionally do not call mch_exit() since this in turn
1547         // will lead to -[MMBackend exit] getting called which we want to
1548         // avoid.
1549         exit(0);
1550     }
1552     ASLogInfo(@"Connection acknowledgement received");
1553     [self processInputQueue];
1556 - (oneway void)acknowledgeConnection
1558     ASLogDebug(@"");
1559     waitForAck = NO;
1562 - (BOOL)imState
1564     return imState;
1567 - (void)setImState:(BOOL)activated
1569     imState = activated;
1572 static void netbeansReadCallback(CFSocketRef s,
1573                                  CFSocketCallBackType callbackType,
1574                                  CFDataRef address,
1575                                  const void *data,
1576                                  void *info)
1578     // NetBeans socket is readable.
1579     [[MMBackend sharedInstance] messageFromNetbeans];
1582 - (void)messageFromNetbeans
1584     [inputQueue addObject:[NSNumber numberWithInt:NetBeansMsgID]];
1585     [inputQueue addObject:[NSNull null]];
1588 - (void)setNetbeansSocket:(int)socket
1590     if (netbeansSocket) {
1591         CFRelease(netbeansSocket);
1592         netbeansSocket = NULL;
1593     }
1595     if (netbeansRunLoopSource) {
1596         CFRunLoopSourceInvalidate(netbeansRunLoopSource);
1597         netbeansRunLoopSource = NULL;
1598     }
1600     if (socket == -1)
1601         return;
1603     // Tell CFRunLoop that we are interested in NetBeans socket input.
1604     netbeansSocket = CFSocketCreateWithNative(kCFAllocatorDefault,
1605                                               socket,
1606                                               kCFSocketReadCallBack,
1607                                               &netbeansReadCallback,
1608                                               NULL);
1609     netbeansRunLoopSource = CFSocketCreateRunLoopSource(NULL,
1610                                                         netbeansSocket,
1611                                                         0);
1612     CFRunLoopAddSource(CFRunLoopGetCurrent(),
1613                        netbeansRunLoopSource,
1614                        kCFRunLoopCommonModes);
1617 @end // MMBackend
1621 @implementation MMBackend (Private)
1623 - (void)clearDrawData
1625     [drawData setLength:0];
1626     numWholeLineChanges = offsetForDrawDataPrune = 0;
1629 - (void)didChangeWholeLine
1631     // It may happen that draw queue is filled up with lots of changes that
1632     // affect a whole row.  If the number of such changes equals twice the
1633     // number of visible rows then we can prune some commands off the queue.
1634     //
1635     // NOTE: If we don't perform this pruning the draw queue may grow
1636     // indefinitely if Vim were to repeatedly send draw commands without ever
1637     // waiting for new input (that's when the draw queue is flushed).  The one
1638     // instance I know where this can happen is when a command is executed in
1639     // the shell (think ":grep" with thousands of matches).
1641     ++numWholeLineChanges;
1642     if (numWholeLineChanges == gui.num_rows) {
1643         // Remember the offset to prune up to.
1644         offsetForDrawDataPrune = [drawData length];
1645     } else if (numWholeLineChanges == 2*gui.num_rows) {
1646         // Delete all the unnecessary draw commands.
1647         NSMutableData *d = [[NSMutableData alloc]
1648                     initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1649                            length:[drawData length] - offsetForDrawDataPrune];
1650         offsetForDrawDataPrune = [d length];
1651         numWholeLineChanges -= gui.num_rows;
1652         [drawData release];
1653         drawData = d;
1654     }
1657 - (void)waitForDialogReturn
1659     // Keep processing the run loop until a dialog returns.  To avoid getting
1660     // stuck in an endless loop (could happen if the setDialogReturn: message
1661     // was lost) we also do some paranoia checks.
1662     //
1663     // Note that in Cocoa the user can still resize windows and select menu
1664     // items while a sheet is being displayed, so we can't just wait for the
1665     // first message to arrive and assume that is the setDialogReturn: call.
1667     while (nil == dialogReturn && !got_int && [connection isValid])
1668         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1669                                  beforeDate:[NSDate distantFuture]];
1671     // Search for any resize messages on the input queue.  All other messages
1672     // on the input queue are dropped.  The reason why we single out resize
1673     // messages is because the user may have resized the window while a sheet
1674     // was open.
1675     int i, count = [inputQueue count];
1676     if (count > 0) {
1677         id textDimData = nil;
1678         if (count%2 == 0) {
1679             for (i = count-2; i >= 0; i -= 2) {
1680                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1681                 if (SetTextDimensionsMsgID == msgid) {
1682                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1683                     break;
1684                 }
1685             }
1686         }
1688         [inputQueue removeAllObjects];
1690         if (textDimData) {
1691             [inputQueue addObject:
1692                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1693             [inputQueue addObject:textDimData];
1694             [textDimData release];
1695         }
1696     }
1699 - (void)insertVimStateMessage
1701     // NOTE: This is the place to add Vim state that needs to be accessed from
1702     // MacVim.  Do not add state that could potentially require lots of memory
1703     // since this message gets sent each time the output queue is forcibly
1704     // flushed (e.g. storing the currently selected text would be a bad idea).
1705     // We take this approach of "pushing" the state to MacVim to avoid having
1706     // to make synchronous calls from MacVim to Vim in order to get state.
1708     BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1709     int numTabs = tabpage_index(NULL) - 1;
1710     if (numTabs < 0)
1711         numTabs = 0;
1713     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1714         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1715         [NSNumber numberWithInt:p_mh], @"p_mh",
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 if (ZoomMsgID == msgid) {
1941         if (!data) return;
1942         const void *bytes = [data bytes];
1943         int rows = *((int*)bytes);  bytes += sizeof(int);
1944         int cols = *((int*)bytes);  bytes += sizeof(int);
1945         //int zoom = *((int*)bytes);  bytes += sizeof(int);
1947         // NOTE: The frontend sends zoom messages here causing us to
1948         // immediately resize the shell and mirror the message back to the
1949         // frontend.  This is done to ensure that the draw commands reach the
1950         // frontend before the window actually changes size in order to avoid
1951         // flickering.  (Also see comment in SetTextDimensionsReplyMsgID
1952         // regarding resizing.)
1953         [self queueMessage:ZoomMsgID data:data];
1954         gui_resize_shell(cols, rows);
1955     } else {
1956         ASLogWarn(@"Unknown message received (msgid=%d)", msgid);
1957     }
1960 - (void)doKeyDown:(NSString *)key
1961           keyCode:(unsigned)code
1962         modifiers:(int)mods
1964     ASLogDebug(@"key='%@' code=%#x mods=%#x length=%d", key, code, mods,
1965             [key length]);
1966     if (!key) return;
1968     char_u *str = (char_u*)[key UTF8String];
1969     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1971     if ([self handleSpecialKey:key keyCode:code modifiers:mods])
1972         return;
1974 #ifdef FEAT_MBYTE
1975     char_u *conv_str = NULL;
1976     if (input_conv.vc_type != CONV_NONE) {
1977         conv_str = string_convert(&input_conv, str, &len);
1978         if (conv_str)
1979             str = conv_str;
1980     }
1981 #endif
1983     if (mods & MOD_MASK_CMD) {
1984         // NOTE: For normal input (non-special, 'macmeta' off) the modifier
1985         // flags are already included in the key event.  However, the Cmd key
1986         // flag is special and must always be added manually.
1987         // The Shift flag is already included in the key when the Command
1988         // key is held.  The same goes for Alt, unless Ctrl is held or
1989         // 'macmeta' is set.  It is important that these flags are cleared
1990         // _after_ special keys have been handled, since they should never be
1991         // cleared for special keys.
1992         mods &= ~MOD_MASK_SHIFT;
1993         if (!(mods & MOD_MASK_CTRL)) {
1994             BOOL mmta = curbuf ? curbuf->b_p_mmta : YES;
1995             if (!mmta)
1996                 mods &= ~MOD_MASK_ALT;
1997         }
1999         ASLogDebug(@"add mods=%#x", mods);
2000         char_u modChars[3] = { CSI, KS_MODIFIER, mods };
2001         add_to_input_buf(modChars, 3);
2002     } else if (mods & MOD_MASK_ALT && 1 == len && str[0] < 0x80
2003             && curbuf && curbuf->b_p_mmta) {
2004         // HACK! The 'macmeta' is set so we have to handle Alt key presses
2005         // separately.  Normally Alt key presses are interpreted by the
2006         // frontend but now we have to manually set the 8th bit and deal with
2007         // UTF-8 conversion.
2008         if ([self handleMacMetaKey:str[0] modifiers:mods])
2009             return;
2010     }
2013     for (i = 0; i < len; ++i) {
2014         ASLogDebug(@"add byte [%d/%d]: %#x", i, len, str[i]);
2015         add_to_input_buf(str+i, 1);
2016         if (CSI == str[i]) {
2017             // NOTE: If the converted string contains the byte CSI, then it
2018             // must be followed by the bytes KS_EXTRA, KE_CSI or things
2019             // won't work.
2020             static char_u extra[2] = { KS_EXTRA, KE_CSI };
2021             ASLogDebug(@"add KS_EXTRA, KE_CSI");
2022             add_to_input_buf(extra, 2);
2023         }
2024     }
2026 #ifdef FEAT_MBYTE
2027     if (conv_str)
2028         vim_free(conv_str);
2029 #endif
2032 - (BOOL)handleSpecialKey:(NSString *)key
2033                  keyCode:(unsigned)code
2034                modifiers:(int)mods
2036     int i;
2037     for (i = 0; special_keys[i].key_sym != 0; i++) {
2038         if (special_keys[i].key_sym == code) {
2039             ASLogDebug(@"Special key: %#x", code);
2040             break;
2041         }
2042     }
2043     if (special_keys[i].key_sym == 0)
2044         return NO;
2046     int ikey = special_keys[i].vim_code1 == NUL ? special_keys[i].vim_code0 :
2047             TO_SPECIAL(special_keys[i].vim_code0, special_keys[i].vim_code1);
2048     ikey = simplify_key(ikey, &mods);
2049     if (ikey == CSI)
2050         ikey = K_CSI;
2052     char_u chars[4];
2053     int len = 0;
2055     if (IS_SPECIAL(ikey)) {
2056         chars[0] = CSI;
2057         chars[1] = K_SECOND(ikey);
2058         chars[2] = K_THIRD(ikey);
2059         len = 3;
2060     } else if (mods & MOD_MASK_ALT && special_keys[i].vim_code1 == 0
2061 #ifdef FEAT_MBYTE
2062             && !enc_dbcs    // TODO: ?  (taken from gui_gtk_x11.c)
2063 #endif
2064             ) {
2065         ASLogDebug(@"Alt special=%d", ikey);
2067         // NOTE: The last entries in the special_keys struct when pressed
2068         // together with Alt need to be handled separately or they will not
2069         // work.
2070         // The following code was gleaned from gui_gtk_x11.c.
2071         mods &= ~MOD_MASK_ALT;
2072         int mkey = 0x80 | ikey;
2073 #ifdef FEAT_MBYTE
2074         if (enc_utf8) {  // TODO: What about other encodings?
2075             // Convert to utf-8
2076             chars[0] = (mkey >> 6) + 0xc0;
2077             chars[1] = mkey & 0xbf;
2078             if (chars[1] == CSI) {
2079                 // We end up here when ikey == ESC
2080                 chars[2] = KS_EXTRA;
2081                 chars[3] = KE_CSI;
2082                 len = 4;
2083             } else {
2084                 len = 2;
2085             }
2086         } else
2087 #endif
2088         {
2089             chars[0] = mkey;
2090             len = 1;
2091         }
2092     } else {
2093         ASLogDebug(@"Just ikey=%d", ikey);
2094         chars[0] = ikey;
2095         len = 1;
2096     }
2098     if (len > 0) {
2099         if (mods) {
2100             ASLogDebug(@"Adding mods to special: %d", mods);
2101             char_u modChars[3] = { CSI, KS_MODIFIER, (char_u)mods };
2102             add_to_input_buf(modChars, 3);
2103         }
2105         ASLogDebug(@"Adding special (%d): %x,%x,%x", len,
2106                 chars[0], chars[1], chars[2]);
2107         add_to_input_buf(chars, len);
2108     }
2110     return YES;
2113 - (BOOL)handleMacMetaKey:(int)ikey modifiers:(int)mods
2115     ASLogDebug(@"ikey=%d mods=%d", ikey, mods);
2117     // This code was taken from gui_w48.c and gui_gtk_x11.c.
2118     char_u string[7];
2119     int ch = simplify_key(ikey, &mods);
2121     // Remove the SHIFT modifier for keys where it's already included,
2122     // e.g., '(' and '*'
2123     if (ch < 0x100 && !isalpha(ch) && isprint(ch))
2124         mods &= ~MOD_MASK_SHIFT;
2126     // Interpret the ALT key as making the key META, include SHIFT, etc.
2127     ch = extract_modifiers(ch, &mods);
2128     if (ch == CSI)
2129         ch = K_CSI;
2131     int len = 0;
2132     if (mods) {
2133         string[len++] = CSI;
2134         string[len++] = KS_MODIFIER;
2135         string[len++] = mods;
2136     }
2138     string[len++] = ch;
2139 #ifdef FEAT_MBYTE
2140     // TODO: What if 'enc' is not "utf-8"?
2141     if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
2142         string[len++] = ch & 0xbf;
2143         string[len-2] = ((unsigned)ch >> 6) + 0xc0;
2144         if (string[len-1] == CSI) {
2145             string[len++] = KS_EXTRA;
2146             string[len++] = (int)KE_CSI;
2147         }
2148     }
2149 #endif
2151     add_to_input_buf(string, len);
2152     return YES;
2155 - (void)queueMessage:(int)msgid data:(NSData *)data
2157     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2158     if (data)
2159         [outputQueue addObject:data];
2160     else
2161         [outputQueue addObject:[NSData data]];
2164 - (void)connectionDidDie:(NSNotification *)notification
2166     // If the main connection to MacVim is lost this means that either MacVim
2167     // has crashed or this process did not receive its termination message
2168     // properly (e.g. if the TerminateNowMsgID was dropped).
2169     //
2170     // NOTE: This is not called if a Vim controller invalidates its connection.
2172     ASLogNotice(@"Main connection was lost before process had a chance "
2173                 "to terminate; preserving swap files.");
2174     getout_preserve_modified(1);
2177 - (void)blinkTimerFired:(NSTimer *)timer
2179     NSTimeInterval timeInterval = 0;
2181     [blinkTimer release];
2182     blinkTimer = nil;
2184     if (MMBlinkStateOn == blinkState) {
2185         gui_undraw_cursor();
2186         blinkState = MMBlinkStateOff;
2187         timeInterval = blinkOffInterval;
2188     } else if (MMBlinkStateOff == blinkState) {
2189         gui_update_cursor(TRUE, FALSE);
2190         blinkState = MMBlinkStateOn;
2191         timeInterval = blinkOnInterval;
2192     }
2194     if (timeInterval > 0) {
2195         blinkTimer = 
2196             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2197                                             selector:@selector(blinkTimerFired:)
2198                                             userInfo:nil repeats:NO] retain];
2199         [self flushQueue:YES];
2200     }
2203 - (void)focusChange:(BOOL)on
2205     gui_focus_change(on);
2208 - (void)handleToggleToolbar
2210     // If 'go' contains 'T', then remove it, else add it.
2212     char_u go[sizeof(GO_ALL)+2];
2213     char_u *p;
2214     int len;
2216     STRCPY(go, p_go);
2217     p = vim_strchr(go, GO_TOOLBAR);
2218     len = STRLEN(go);
2220     if (p != NULL) {
2221         char_u *end = go + len;
2222         while (p < end) {
2223             p[0] = p[1];
2224             ++p;
2225         }
2226     } else {
2227         go[len] = GO_TOOLBAR;
2228         go[len+1] = NUL;
2229     }
2231     set_option_value((char_u*)"guioptions", 0, go, 0);
2234 - (void)handleScrollbarEvent:(NSData *)data
2236     if (!data) return;
2238     const void *bytes = [data bytes];
2239     int32_t ident = *((int32_t*)bytes);  bytes += sizeof(int32_t);
2240     int hitPart = *((int*)bytes);  bytes += sizeof(int);
2241     float fval = *((float*)bytes);  bytes += sizeof(float);
2242     scrollbar_T *sb = gui_find_scrollbar(ident);
2244     if (sb) {
2245         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2246         long value = sb_info->value;
2247         long size = sb_info->size;
2248         long max = sb_info->max;
2249         BOOL isStillDragging = NO;
2250         BOOL updateKnob = YES;
2252         switch (hitPart) {
2253         case NSScrollerDecrementPage:
2254             value -= (size > 2 ? size - 2 : 1);
2255             break;
2256         case NSScrollerIncrementPage:
2257             value += (size > 2 ? size - 2 : 1);
2258             break;
2259         case NSScrollerDecrementLine:
2260             --value;
2261             break;
2262         case NSScrollerIncrementLine:
2263             ++value;
2264             break;
2265         case NSScrollerKnob:
2266             isStillDragging = YES;
2267             // fall through ...
2268         case NSScrollerKnobSlot:
2269             value = (long)(fval * (max - size + 1));
2270             // fall through ...
2271         default:
2272             updateKnob = NO;
2273             break;
2274         }
2276         gui_drag_scrollbar(sb, value, isStillDragging);
2278         if (updateKnob) {
2279             // Dragging the knob or option+clicking automatically updates
2280             // the knob position (on the actual NSScroller), so we only
2281             // need to set the knob position in the other cases.
2282             if (sb->wp) {
2283                 // Update both the left&right vertical scrollbars.
2284                 int32_t idL = (int32_t)sb->wp->w_scrollbars[SBAR_LEFT].ident;
2285                 int32_t idR = (int32_t)sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2286                 [self setScrollbarThumbValue:value size:size max:max
2287                                   identifier:idL];
2288                 [self setScrollbarThumbValue:value size:size max:max
2289                                   identifier:idR];
2290             } else {
2291                 // Update the horizontal scrollbar.
2292                 [self setScrollbarThumbValue:value size:size max:max
2293                                   identifier:ident];
2294             }
2295         }
2296     }
2299 - (void)handleSetFont:(NSData *)data
2301     if (!data) return;
2303     const void *bytes = [data bytes];
2304     int pointSize = (int)*((float*)bytes);  bytes += sizeof(float);
2306     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2307     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2308     bytes += len;
2310     [name appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2311     char_u *s = (char_u*)[name UTF8String];
2313     unsigned wlen = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2314     char_u *ws = NULL;
2315     if (wlen > 0) {
2316         NSMutableString *wname = [NSMutableString stringWithUTF8String:bytes];
2317         bytes += wlen;
2319         [wname appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2320         ws = (char_u*)[wname UTF8String];
2321     }
2323 #ifdef FEAT_MBYTE
2324     s = CONVERT_FROM_UTF8(s);
2325     if (ws) {
2326         ws = CONVERT_FROM_UTF8(ws);
2327     }
2328 #endif
2330     set_option_value((char_u*)"guifont", 0, s, 0);
2332     if (ws && gui.wide_font != NOFONT) {
2333         // NOTE: This message is sent on Cmd-+/Cmd-- and as such should only
2334         // change the wide font if 'gfw' is non-empty (the frontend always has
2335         // some wide font set, even if 'gfw' is empty).
2336         set_option_value((char_u*)"guifontwide", 0, ws, 0);
2337     }
2339 #ifdef FEAT_MBYTE
2340     if (ws) {
2341         CONVERT_FROM_UTF8_FREE(ws);
2342     }
2343     CONVERT_FROM_UTF8_FREE(s);
2344 #endif
2346     [self redrawScreen];
2349 - (void)handleDropFiles:(NSData *)data
2351     // TODO: Get rid of this method; instead use Vim script directly.  At the
2352     // moment I know how to do this to open files in tabs, but I'm not sure how
2353     // to add the filenames to the command line when in command line mode.
2355     if (!data) return;
2357     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2358     if (!args) return;
2360     id obj = [args objectForKey:@"forceOpen"];
2361     BOOL forceOpen = YES;
2362     if (obj)
2363         forceOpen = [obj boolValue];
2365     NSArray *filenames = [args objectForKey:@"filenames"];
2366     if (!(filenames && [filenames count] > 0)) return;
2368 #ifdef FEAT_DND
2369     if (!forceOpen && (State & CMDLINE)) {
2370         // HACK!  If Vim is in command line mode then the files names
2371         // should be added to the command line, instead of opening the
2372         // files in tabs (unless forceOpen is set).  This is taken care of by
2373         // gui_handle_drop().
2374         int n = [filenames count];
2375         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2376         if (fnames) {
2377             int i = 0;
2378             for (i = 0; i < n; ++i)
2379                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2381             // NOTE!  This function will free 'fnames'.
2382             // HACK!  It is assumed that the 'x' and 'y' arguments are
2383             // unused when in command line mode.
2384             gui_handle_drop(0, 0, 0, fnames, n);
2385         }
2386     } else
2387 #endif // FEAT_DND
2388     {
2389         [self handleOpenWithArguments:args];
2390     }
2393 - (void)handleDropString:(NSData *)data
2395     if (!data) return;
2397 #ifdef FEAT_DND
2398     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2399     const void *bytes = [data bytes];
2400     int len = *((int*)bytes);  bytes += sizeof(int);
2401     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2403     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2404     NSRange range = { 0, [string length] };
2405     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2406                                          withString:@"\x0a" options:0
2407                                               range:range];
2408     if (0 == n) {
2409         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2410                                        options:0 range:range];
2411     }
2413     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2414     char_u *s = (char_u*)[string UTF8String];
2415 #ifdef FEAT_MBYTE
2416     if (input_conv.vc_type != CONV_NONE)
2417         s = string_convert(&input_conv, s, &len);
2418 #endif
2419     dnd_yank_drag_data(s, len);
2420 #ifdef FEAT_MBYTE
2421     if (input_conv.vc_type != CONV_NONE)
2422         vim_free(s);
2423 #endif
2424     add_to_input_buf(dropkey, sizeof(dropkey));
2425 #endif // FEAT_DND
2428 - (void)startOdbEditWithArguments:(NSDictionary *)args
2430 #ifdef FEAT_ODB_EDITOR
2431     id obj = [args objectForKey:@"remoteID"];
2432     if (!obj) return;
2434     OSType serverID = [obj unsignedIntValue];
2435     NSString *remotePath = [args objectForKey:@"remotePath"];
2437     NSAppleEventDescriptor *token = nil;
2438     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2439     obj = [args objectForKey:@"remoteTokenDescType"];
2440     if (tokenData && obj) {
2441         DescType tokenType = [obj unsignedLongValue];
2442         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2443                                                                 data:tokenData];
2444     }
2446     NSArray *filenames = [args objectForKey:@"filenames"];
2447     unsigned i, numFiles = [filenames count];
2448     for (i = 0; i < numFiles; ++i) {
2449         NSString *filename = [filenames objectAtIndex:i];
2450         char_u *s = [filename vimStringSave];
2451         buf_T *buf = buflist_findname(s);
2452         vim_free(s);
2454         if (buf) {
2455             if (buf->b_odb_token) {
2456                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2457                 buf->b_odb_token = NULL;
2458             }
2460             if (buf->b_odb_fname) {
2461                 vim_free(buf->b_odb_fname);
2462                 buf->b_odb_fname = NULL;
2463             }
2465             buf->b_odb_server_id = serverID;
2467             if (token)
2468                 buf->b_odb_token = [token retain];
2469             if (remotePath)
2470                 buf->b_odb_fname = [remotePath vimStringSave];
2471         } else {
2472             ASLogWarn(@"Could not find buffer '%@' for ODB editing.", filename);
2473         }
2474     }
2475 #endif // FEAT_ODB_EDITOR
2478 - (void)handleXcodeMod:(NSData *)data
2480 #if 0
2481     const void *bytes = [data bytes];
2482     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2483     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2484     if (0 == len)
2485         return;
2487     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2488             descriptorWithDescriptorType:type
2489                                    bytes:bytes
2490                                   length:len];
2491 #endif
2494 - (void)handleOpenWithArguments:(NSDictionary *)args
2496     // ARGUMENT:                DESCRIPTION:
2497     // -------------------------------------------------------------
2498     // filenames                list of filenames
2499     // dontOpen                 don't open files specified in above argument
2500     // layout                   which layout to use to open files
2501     // selectionRange           range of lines to select
2502     // searchText               string to search for
2503     // cursorLine               line to position the cursor on
2504     // cursorColumn             column to position the cursor on
2505     //                          (only valid when "cursorLine" is set)
2506     // remoteID                 ODB parameter
2507     // remotePath               ODB parameter
2508     // remoteTokenDescType      ODB parameter
2509     // remoteTokenData          ODB parameter
2511     ASLogDebug(@"args=%@ (starting=%d)", args, starting);
2513     NSArray *filenames = [args objectForKey:@"filenames"];
2514     int i, numFiles = filenames ? [filenames count] : 0;
2515     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2516     int layout = [[args objectForKey:@"layout"] intValue];
2518     // Change to directory of first file to open if this is an "unused" editor
2519     // (but do not do this if editing remotely).
2520     if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2521             && (starting || [self unusedEditor]) ) {
2522         char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2523         if (mch_isdir(s)) {
2524             mch_chdir((char*)s);
2525         } else {
2526             vim_chdirfile(s);
2527         }
2528         vim_free(s);
2529     }
2531     if (starting > 0) {
2532         // When Vim is starting we simply add the files to be opened to the
2533         // global arglist and Vim will take care of opening them for us.
2534         if (openFiles && numFiles > 0) {
2535             for (i = 0; i < numFiles; i++) {
2536                 NSString *fname = [filenames objectAtIndex:i];
2537                 char_u *p = NULL;
2539                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2540                         || (p = [fname vimStringSave]) == NULL)
2541                     exit(2); // See comment in -[MMBackend exit]
2542                 else
2543                     alist_add(&global_alist, p, 2);
2544             }
2546             // Vim will take care of arranging the files added to the arglist
2547             // in windows or tabs; all we must do is to specify which layout to
2548             // use.
2549             initialWindowLayout = layout;
2550         }
2551     } else {
2552         // When Vim is already open we resort to some trickery to open the
2553         // files with the specified layout.
2554         //
2555         // TODO: Figure out a better way to handle this?
2556         if (openFiles && numFiles > 0) {
2557             BOOL oneWindowInTab = topframe ? YES
2558                                            : (topframe->fr_layout == FR_LEAF);
2559             BOOL bufChanged = NO;
2560             BOOL bufHasFilename = NO;
2561             if (curbuf) {
2562                 bufChanged = curbufIsChanged();
2563                 bufHasFilename = curbuf->b_ffname != NULL;
2564             }
2566             // Temporarily disable flushing since the following code may
2567             // potentially cause multiple redraws.
2568             flushDisabled = YES;
2570             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2571             if (WIN_TABS == layout && !onlyOneTab) {
2572                 // By going to the last tabpage we ensure that the new tabs
2573                 // will appear last (if this call is left out, the taborder
2574                 // becomes messy).
2575                 goto_tabpage(9999);
2576             }
2578             // Make sure we're in normal mode first.
2579             [self addInput:@"<C-\\><C-N>"];
2581             if (numFiles > 1) {
2582                 // With "split layout" we open a new tab before opening
2583                 // multiple files if the current tab has more than one window
2584                 // or if there is exactly one window but whose buffer has a
2585                 // filename.  (The :drop command ensures modified buffers get
2586                 // their own window.)
2587                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2588                         (!oneWindowInTab || bufHasFilename))
2589                     [self addInput:@":tabnew<CR>"];
2591                 // The files are opened by constructing a ":drop ..." command
2592                 // and executing it.
2593                 NSMutableString *cmd = (WIN_TABS == layout)
2594                         ? [NSMutableString stringWithString:@":tab drop"]
2595                         : [NSMutableString stringWithString:@":drop"];
2597                 for (i = 0; i < numFiles; ++i) {
2598                     NSString *file = [filenames objectAtIndex:i];
2599                     file = [file stringByEscapingSpecialFilenameCharacters];
2600                     [cmd appendString:@" "];
2601                     [cmd appendString:file];
2602                 }
2604                 // Temporarily clear 'suffixes' so that the files are opened in
2605                 // the same order as they appear in the "filenames" array.
2606                 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2608                 [self addInput:cmd];
2610                 // Split the view into multiple windows if requested.
2611                 if (WIN_HOR == layout)
2612                     [self addInput:@"|sall"];
2613                 else if (WIN_VER == layout)
2614                     [self addInput:@"|vert sall"];
2616                 // Restore the old value of 'suffixes'.
2617                 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2618             } else {
2619                 // When opening one file we try to reuse the current window,
2620                 // but not if its buffer is modified or has a filename.
2621                 // However, the 'arglist' layout always opens the file in the
2622                 // current window.
2623                 NSString *file = [[filenames lastObject]
2624                         stringByEscapingSpecialFilenameCharacters];
2625                 NSString *cmd;
2626                 if (WIN_HOR == layout) {
2627                     if (!(bufHasFilename || bufChanged))
2628                         cmd = [NSString stringWithFormat:@":e %@", file];
2629                     else
2630                         cmd = [NSString stringWithFormat:@":sp %@", file];
2631                 } else if (WIN_VER == layout) {
2632                     if (!(bufHasFilename || bufChanged))
2633                         cmd = [NSString stringWithFormat:@":e %@", file];
2634                     else
2635                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2636                 } else if (WIN_TABS == layout) {
2637                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2638                         cmd = [NSString stringWithFormat:@":e %@", file];
2639                     else
2640                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2641                 } else {
2642                     // (The :drop command will split if there is a modified
2643                     // buffer.)
2644                     cmd = [NSString stringWithFormat:@":drop %@", file];
2645                 }
2647                 [self addInput:cmd];
2648                 [self addInput:@"<CR>"];
2649             }
2651             // Force screen redraw (does it have to be this complicated?).
2652             // (This code was taken from the end of gui_handle_drop().)
2653             update_screen(NOT_VALID);
2654             setcursor();
2655             out_flush();
2656             gui_update_cursor(FALSE, FALSE);
2657             maketitle();
2659             flushDisabled = NO;
2660         }
2661     }
2663     if ([args objectForKey:@"remoteID"]) {
2664         // NOTE: We have to delay processing any ODB related arguments since
2665         // the file(s) may not be opened until the input buffer is processed.
2666         [self performSelector:@selector(startOdbEditWithArguments:)
2667                    withObject:args
2668                    afterDelay:0];
2669     }
2671     NSString *lineString = [args objectForKey:@"cursorLine"];
2672     if (lineString && [lineString intValue] > 0) {
2673         NSString *columnString = [args objectForKey:@"cursorColumn"];
2674         if (!(columnString && [columnString intValue] > 0))
2675             columnString = @"1";
2677         NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2678                 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2679         [self addInput:cmd];
2680     }
2682     NSString *rangeString = [args objectForKey:@"selectionRange"];
2683     if (rangeString) {
2684         // Build a command line string that will select the given range of
2685         // lines.  If range.length == 0, then position the cursor on the given
2686         // line but do not select.
2687         NSRange range = NSRangeFromString(rangeString);
2688         NSString *cmd;
2689         if (range.length > 0) {
2690             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2691                     NSMaxRange(range), range.location];
2692         } else {
2693             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2694                     range.location];
2695         }
2697         [self addInput:cmd];
2698     }
2700     NSString *searchText = [args objectForKey:@"searchText"];
2701     if (searchText) {
2702         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2703                 searchText]];
2704     }
2707 - (BOOL)checkForModifiedBuffers
2709     buf_T *buf;
2710     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2711         if (bufIsChanged(buf)) {
2712             return YES;
2713         }
2714     }
2716     return NO;
2719 - (void)addInput:(NSString *)input
2721     // NOTE: This code is essentially identical to server_to_input_buf(),
2722     // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2723     char_u *string = [input vimStringSave];
2724     if (!string) return;
2726     /* Set 'cpoptions' the way we want it.
2727      *    B set - backslashes are *not* treated specially
2728      *    k set - keycodes are *not* reverse-engineered
2729      *    < unset - <Key> sequences *are* interpreted
2730      *  The last but one parameter of replace_termcodes() is TRUE so that the
2731      *  <lt> sequence is recognised - needed for a real backslash.
2732      */
2733     char_u *ptr = NULL;
2734     char_u *cpo_save = p_cpo;
2735     p_cpo = (char_u *)"Bk";
2736     char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2737     p_cpo = cpo_save;
2739     if (*ptr != NUL)    /* trailing CTRL-V results in nothing */
2740     {
2741         /*
2742          * Add the string to the input stream.
2743          * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2744          *
2745          * First clear typed characters from the typeahead buffer, there could
2746          * be half a mapping there.  Then append to the existing string, so
2747          * that multiple commands from a client are concatenated.
2748          */
2749         if (typebuf.tb_maplen < typebuf.tb_len)
2750             del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2751         (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2753         /* Let input_available() know we inserted text in the typeahead
2754          * buffer. */
2755         typebuf_was_filled = TRUE;
2756     }
2757     vim_free(ptr);
2758     vim_free(string);
2761 - (BOOL)unusedEditor
2763     BOOL oneWindowInTab = topframe ? YES
2764                                    : (topframe->fr_layout == FR_LEAF);
2765     BOOL bufChanged = NO;
2766     BOOL bufHasFilename = NO;
2767     if (curbuf) {
2768         bufChanged = curbufIsChanged();
2769         bufHasFilename = curbuf->b_ffname != NULL;
2770     }
2772     BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2774     return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2777 - (void)redrawScreen
2779     // Force screen redraw (does it have to be this complicated?).
2780     redraw_all_later(CLEAR);
2781     update_screen(NOT_VALID);
2782     setcursor();
2783     out_flush();
2784     gui_update_cursor(FALSE, FALSE);
2786     // HACK! The cursor is not put back at the command line by the above
2787     // "redraw commands".  The following test seems to do the trick though.
2788     if (State & CMDLINE)
2789         redrawcmdline();
2792 - (void)handleFindReplace:(NSDictionary *)args
2794     if (!args) return;
2796     NSString *findString = [args objectForKey:@"find"];
2797     if (!findString) return;
2799     char_u *find = [findString vimStringSave];
2800     char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2801     int flags = [[args objectForKey:@"flags"] intValue];
2803     // NOTE: The flag 0x100 is used to indicate a backward search.
2804     gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2806     vim_free(find);
2807     vim_free(replace);
2811 - (void)handleMarkedText:(NSData *)data
2813     const void *bytes = [data bytes];
2814     int32_t pos = *((int32_t*)bytes);  bytes += sizeof(int32_t);
2815     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2816     char *chars = (char *)bytes;
2818     ASLogDebug(@"pos=%d len=%d chars=%s", pos, len, chars);
2820     if (pos < 0) {
2821         im_preedit_abandon_macvim();
2822     } else if (len == 0) {
2823         im_preedit_end_macvim();
2824     } else {
2825         if (!preedit_get_status())
2826             im_preedit_start_macvim();
2828         im_preedit_changed_macvim(chars, pos);
2829     }
2832 @end // MMBackend (Private)
2837 @implementation MMBackend (ClientServer)
2839 - (NSString *)connectionNameFromServerName:(NSString *)name
2841     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2843     return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2844         lowercaseString];
2847 - (NSConnection *)connectionForServerName:(NSString *)name
2849     // TODO: Try 'name%d' if 'name' fails.
2850     NSString *connName = [self connectionNameFromServerName:name];
2851     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2853     if (!svrConn) {
2854         svrConn = [NSConnection connectionWithRegisteredName:connName
2855                                                            host:nil];
2856         // Try alternate server...
2857         if (!svrConn && alternateServerName) {
2858             ASLogInfo(@"  trying to connect to alternate server: %@",
2859                       alternateServerName);
2860             connName = [self connectionNameFromServerName:alternateServerName];
2861             svrConn = [NSConnection connectionWithRegisteredName:connName
2862                                                             host:nil];
2863         }
2865         // Try looking for alternate servers...
2866         if (!svrConn) {
2867             ASLogInfo(@"  looking for alternate servers...");
2868             NSString *alt = [self alternateServerNameForName:name];
2869             if (alt != alternateServerName) {
2870                 ASLogInfo(@"  found alternate server: %@", alt);
2871                 [alternateServerName release];
2872                 alternateServerName = [alt copy];
2873             }
2874         }
2876         // Try alternate server again...
2877         if (!svrConn && alternateServerName) {
2878             ASLogInfo(@"  trying to connect to alternate server: %@",
2879                       alternateServerName);
2880             connName = [self connectionNameFromServerName:alternateServerName];
2881             svrConn = [NSConnection connectionWithRegisteredName:connName
2882                                                             host:nil];
2883         }
2885         if (svrConn) {
2886             [connectionNameDict setObject:svrConn forKey:connName];
2888             ASLogDebug(@"Adding %@ as connection observer for %@",
2889                        self, svrConn);
2890             [[NSNotificationCenter defaultCenter] addObserver:self
2891                     selector:@selector(serverConnectionDidDie:)
2892                         name:NSConnectionDidDieNotification object:svrConn];
2893         }
2894     }
2896     return svrConn;
2899 - (NSConnection *)connectionForServerPort:(int)port
2901     NSConnection *conn;
2902     NSEnumerator *e = [connectionNameDict objectEnumerator];
2904     while ((conn = [e nextObject])) {
2905         // HACK! Assume connection uses mach ports.
2906         if (port == [(NSMachPort*)[conn sendPort] machPort])
2907             return conn;
2908     }
2910     return nil;
2913 - (void)serverConnectionDidDie:(NSNotification *)notification
2915     ASLogDebug(@"notification=%@", notification);
2917     NSConnection *svrConn = [notification object];
2919     ASLogDebug(@"Removing %@ as connection observer from %@", self, svrConn);
2920     [[NSNotificationCenter defaultCenter]
2921             removeObserver:self
2922                       name:NSConnectionDidDieNotification
2923                     object:svrConn];
2925     [connectionNameDict removeObjectsForKeys:
2926         [connectionNameDict allKeysForObject:svrConn]];
2928     // HACK! Assume connection uses mach ports.
2929     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2930     NSNumber *key = [NSNumber numberWithInt:port];
2932     [clientProxyDict removeObjectForKey:key];
2933     [serverReplyDict removeObjectForKey:key];
2936 - (void)addClient:(NSDistantObject *)client
2938     NSConnection *conn = [client connectionForProxy];
2939     // HACK! Assume connection uses mach ports.
2940     int port = [(NSMachPort*)[conn sendPort] machPort];
2941     NSNumber *key = [NSNumber numberWithInt:port];
2943     if (![clientProxyDict objectForKey:key]) {
2944         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2945         [clientProxyDict setObject:client forKey:key];
2946     }
2948     // NOTE: 'clientWindow' is a global variable which is used by <client>
2949     clientWindow = port;
2952 - (NSString *)alternateServerNameForName:(NSString *)name
2954     if (!(name && [name length] > 0))
2955         return nil;
2957     // Only look for alternates if 'name' doesn't end in a digit.
2958     unichar lastChar = [name characterAtIndex:[name length]-1];
2959     if (lastChar >= '0' && lastChar <= '9')
2960         return nil;
2962     // Look for alternates among all current servers.
2963     NSArray *list = [self serverList];
2964     if (!(list && [list count] > 0))
2965         return nil;
2967     // Filter out servers starting with 'name' and ending with a number. The
2968     // (?i) pattern ensures that the match is case insensitive.
2969     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2970     NSPredicate *pred = [NSPredicate predicateWithFormat:
2971             @"SELF MATCHES %@", pat];
2972     list = [list filteredArrayUsingPredicate:pred];
2973     if ([list count] > 0) {
2974         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2975         return [list objectAtIndex:0];
2976     }
2978     return nil;
2981 @end // MMBackend (ClientServer)
2986 @implementation NSString (MMServerNameCompare)
2987 - (NSComparisonResult)serverNameCompare:(NSString *)string
2989     return [self compare:string
2990                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2992 @end
2997 static int eventModifierFlagsToVimModMask(int modifierFlags)
2999     int modMask = 0;
3001     if (modifierFlags & NSShiftKeyMask)
3002         modMask |= MOD_MASK_SHIFT;
3003     if (modifierFlags & NSControlKeyMask)
3004         modMask |= MOD_MASK_CTRL;
3005     if (modifierFlags & NSAlternateKeyMask)
3006         modMask |= MOD_MASK_ALT;
3007     if (modifierFlags & NSCommandKeyMask)
3008         modMask |= MOD_MASK_CMD;
3010     return modMask;
3013 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
3015     int modMask = 0;
3017     if (modifierFlags & NSShiftKeyMask)
3018         modMask |= MOUSE_SHIFT;
3019     if (modifierFlags & NSControlKeyMask)
3020         modMask |= MOUSE_CTRL;
3021     if (modifierFlags & NSAlternateKeyMask)
3022         modMask |= MOUSE_ALT;
3024     return modMask;
3027 static int eventButtonNumberToVimMouseButton(int buttonNumber)
3029     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
3031     return (buttonNumber >= 0 && buttonNumber < 3)
3032             ? mouseButton[buttonNumber] : -1;
3037 // This function is modeled after the VimToPython function found in if_python.c
3038 // NB This does a deep copy by value, it does not lookup references like the
3039 // VimToPython function does.  This is because I didn't want to deal with the
3040 // retain cycles that this would create, and we can cover 99% of the use cases
3041 // by ignoring it.  If we ever switch to using GC in MacVim then this
3042 // functionality can be implemented easily.
3043 static id vimToCocoa(typval_T * tv, int depth)
3045     id result = nil;
3046     id newObj = nil;
3049     // Avoid infinite recursion
3050     if (depth > 100) {
3051         return nil;
3052     }
3054     if (tv->v_type == VAR_STRING) {
3055         char_u * val = tv->vval.v_string;
3056         // val can be NULL if the string is empty
3057         if (!val) {
3058             result = [NSString string];
3059         } else {
3060 #ifdef FEAT_MBYTE
3061             val = CONVERT_TO_UTF8(val);
3062 #endif
3063             result = [NSString stringWithUTF8String:(char*)val];
3064 #ifdef FEAT_MBYTE
3065             CONVERT_TO_UTF8_FREE(val);
3066 #endif
3067         }
3068     } else if (tv->v_type == VAR_NUMBER) {
3069         // looks like sizeof(varnumber_T) is always <= sizeof(long)
3070         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
3071     } else if (tv->v_type == VAR_LIST) {
3072         list_T * list = tv->vval.v_list;
3073         listitem_T * curr;
3075         NSMutableArray * arr = result = [NSMutableArray array];
3077         if (list != NULL) {
3078             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
3079                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
3080                 [arr addObject:newObj];
3081             }
3082         }
3083     } else if (tv->v_type == VAR_DICT) {
3084         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
3086         if (tv->vval.v_dict != NULL) {
3087             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
3088             int todo = ht->ht_used;
3089             hashitem_T * hi;
3090             dictitem_T * di;
3092             for (hi = ht->ht_array; todo > 0; ++hi) {
3093                 if (!HASHITEM_EMPTY(hi)) {
3094                     --todo;
3096                     di = dict_lookup(hi);
3097                     newObj = vimToCocoa(&di->di_tv, depth + 1);
3099                     char_u * keyval = hi->hi_key;
3100 #ifdef FEAT_MBYTE
3101                     keyval = CONVERT_TO_UTF8(keyval);
3102 #endif
3103                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
3104 #ifdef FEAT_MBYTE
3105                     CONVERT_TO_UTF8_FREE(keyval);
3106 #endif
3107                     [dict setObject:newObj forKey:key];
3108                 }
3109             }
3110         }
3111     } else { // only func refs should fall into this category?
3112         result = nil;
3113     }
3115     return result;
3119 // This function is modeled after eval_client_expr_to_string found in main.c
3120 // Returns nil if there was an error evaluating the expression, and writes a
3121 // message to errorStr.
3122 // TODO Get the error that occurred while evaluating the expression in vim
3123 // somehow.
3124 static id evalExprCocoa(NSString * expr, NSString ** errstr)
3127     char_u *s = (char_u*)[expr UTF8String];
3129 #ifdef FEAT_MBYTE
3130     s = CONVERT_FROM_UTF8(s);
3131 #endif
3133     int save_dbl = debug_break_level;
3134     int save_ro = redir_off;
3136     debug_break_level = -1;
3137     redir_off = 0;
3138     ++emsg_skip;
3140     typval_T * tvres = eval_expr(s, NULL);
3142     debug_break_level = save_dbl;
3143     redir_off = save_ro;
3144     --emsg_skip;
3146     setcursor();
3147     out_flush();
3149 #ifdef FEAT_MBYTE
3150     CONVERT_FROM_UTF8_FREE(s);
3151 #endif
3153 #ifdef FEAT_GUI
3154     if (gui.in_use)
3155         gui_update_cursor(FALSE, FALSE);
3156 #endif
3158     if (tvres == NULL) {
3159         free_tv(tvres);
3160         *errstr = @"Expression evaluation failed.";
3161     }
3163     id res = vimToCocoa(tvres, 1);
3165     free_tv(tvres);
3167     if (res == nil) {
3168         *errstr = @"Conversion to cocoa values failed.";
3169     }
3171     return res;
3176 @implementation NSString (VimStrings)
3178 + (id)stringWithVimString:(char_u *)s
3180     // This method ensures a non-nil string is returned.  If 's' cannot be
3181     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
3182     // still fails an empty NSString is returned.
3183     NSString *string = nil;
3184     if (s) {
3185 #ifdef FEAT_MBYTE
3186         s = CONVERT_TO_UTF8(s);
3187 #endif
3188         string = [NSString stringWithUTF8String:(char*)s];
3189         if (!string) {
3190             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
3191             // latin-1?
3192             string = [NSString stringWithCString:(char*)s
3193                                         encoding:NSISOLatin1StringEncoding];
3194         }
3195 #ifdef FEAT_MBYTE
3196         CONVERT_TO_UTF8_FREE(s);
3197 #endif
3198     }
3200     return string != nil ? string : [NSString string];
3203 - (char_u *)vimStringSave
3205     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3207 #ifdef FEAT_MBYTE
3208     s = CONVERT_FROM_UTF8(s);
3209 #endif
3210     ret = vim_strsave(s);
3211 #ifdef FEAT_MBYTE
3212     CONVERT_FROM_UTF8_FREE(s);
3213 #endif
3215     return ret;
3218 @end // NSString (VimStrings)