Merge branch 'KaoriYa'
[MacVim/KaoriYa.git] / src / MacVim / MMBackend.m
blob318f3b62f218b825f5af0d9a00802a02c236bbef
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:[self unusedEditor]], @"unusedEditor",
1717         [NSNumber numberWithBool:mmta], @"p_mmta",
1718         [NSNumber numberWithInt:numTabs], @"numTabs",
1719         nil];
1721     // Put the state before all other messages.
1722     int msgid = SetVimStateMsgID;
1723     [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1724     [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1725                       atIndex:0];
1728 - (void)processInputQueue
1730     if ([inputQueue count] == 0) return;
1732     // NOTE: One of the input events may cause this method to be called
1733     // recursively, so copy the input queue to a local variable and clear the
1734     // queue before starting to process input events (otherwise we could get
1735     // stuck in an endless loop).
1736     NSArray *q = [inputQueue copy];
1737     unsigned i, count = [q count];
1739     [inputQueue removeAllObjects];
1741     for (i = 1; i < count; i+=2) {
1742         int msgid = [[q objectAtIndex:i-1] intValue];
1743         id data = [q objectAtIndex:i];
1744         if ([data isEqual:[NSNull null]])
1745             data = nil;
1747         ASLogDebug(@"(%d) %s", i, MessageStrings[msgid]);
1748         [self handleInputEvent:msgid data:data];
1749     }
1751     [q release];
1755 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1757     if (KeyDownMsgID == msgid) {
1758         if (!data) return;
1759         const void *bytes = [data bytes];
1760         unsigned mods = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1761         unsigned code = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1762         unsigned len  = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1763         NSString *key = [[NSString alloc] initWithBytes:bytes
1764                                                  length:len
1765                                                encoding:NSUTF8StringEncoding];
1766         mods = eventModifierFlagsToVimModMask(mods);
1768         [self doKeyDown:key keyCode:code modifiers:mods];
1769         [key release];
1770     } else if (ScrollWheelMsgID == msgid) {
1771         if (!data) return;
1772         const void *bytes = [data bytes];
1774         int row = *((int*)bytes);  bytes += sizeof(int);
1775         int col = *((int*)bytes);  bytes += sizeof(int);
1776         int flags = *((int*)bytes);  bytes += sizeof(int);
1777         float dy = *((float*)bytes);  bytes += sizeof(float);
1779         int button = MOUSE_5;
1780         if (dy > 0) button = MOUSE_4;
1782         flags = eventModifierFlagsToVimMouseModMask(flags);
1784         int numLines = (int)round(dy);
1785         if (numLines < 0) numLines = -numLines;
1786         if (numLines == 0) numLines = 1;
1788 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1789         gui.scroll_wheel_force = numLines;
1790 #endif
1792         gui_send_mouse_event(button, col, row, NO, flags);
1793     } else if (MouseDownMsgID == msgid) {
1794         if (!data) return;
1795         const void *bytes = [data bytes];
1797         int row = *((int*)bytes);  bytes += sizeof(int);
1798         int col = *((int*)bytes);  bytes += sizeof(int);
1799         int button = *((int*)bytes);  bytes += sizeof(int);
1800         int flags = *((int*)bytes);  bytes += sizeof(int);
1801         int count = *((int*)bytes);  bytes += sizeof(int);
1803         button = eventButtonNumberToVimMouseButton(button);
1804         if (button >= 0) {
1805             flags = eventModifierFlagsToVimMouseModMask(flags);
1806             gui_send_mouse_event(button, col, row, count>1, flags);
1807         }
1808     } else if (MouseUpMsgID == msgid) {
1809         if (!data) return;
1810         const void *bytes = [data bytes];
1812         int row = *((int*)bytes);  bytes += sizeof(int);
1813         int col = *((int*)bytes);  bytes += sizeof(int);
1814         int flags = *((int*)bytes);  bytes += sizeof(int);
1816         flags = eventModifierFlagsToVimMouseModMask(flags);
1818         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1819     } else if (MouseDraggedMsgID == msgid) {
1820         if (!data) return;
1821         const void *bytes = [data bytes];
1823         int row = *((int*)bytes);  bytes += sizeof(int);
1824         int col = *((int*)bytes);  bytes += sizeof(int);
1825         int flags = *((int*)bytes);  bytes += sizeof(int);
1827         flags = eventModifierFlagsToVimMouseModMask(flags);
1829         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1830     } else if (MouseMovedMsgID == msgid) {
1831         const void *bytes = [data bytes];
1832         int row = *((int*)bytes);  bytes += sizeof(int);
1833         int col = *((int*)bytes);  bytes += sizeof(int);
1835         gui_mouse_moved(col, row);
1836     } else if (AddInputMsgID == msgid) {
1837         NSString *string = [[NSString alloc] initWithData:data
1838                 encoding:NSUTF8StringEncoding];
1839         if (string) {
1840             [self addInput:string];
1841             [string release];
1842         }
1843     } else if (SelectTabMsgID == msgid) {
1844         if (!data) return;
1845         const void *bytes = [data bytes];
1846         int idx = *((int*)bytes) + 1;
1847         send_tabline_event(idx);
1848     } else if (CloseTabMsgID == msgid) {
1849         if (!data) return;
1850         const void *bytes = [data bytes];
1851         int idx = *((int*)bytes) + 1;
1852         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1853     } else if (AddNewTabMsgID == msgid) {
1854         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1855     } else if (DraggedTabMsgID == msgid) {
1856         if (!data) return;
1857         const void *bytes = [data bytes];
1858         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1859         // based.
1860         int idx = *((int*)bytes);
1862         tabpage_move(idx);
1863     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1864             || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1865         if (!data) return;
1866         const void *bytes = [data bytes];
1867         int rows = Rows;
1868         if (SetTextColumnsMsgID != msgid) {
1869             rows = *((int*)bytes);  bytes += sizeof(int);
1870         }
1871         int cols = Columns;
1872         if (SetTextRowsMsgID != msgid) {
1873             cols = *((int*)bytes);  bytes += sizeof(int);
1874         }
1876         NSData *d = data;
1877         if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1878             int dim[2] = { rows, cols };
1879             d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1880             msgid = SetTextDimensionsReplyMsgID;
1881         }
1883         if (SetTextDimensionsMsgID == msgid)
1884             msgid = SetTextDimensionsReplyMsgID;
1886         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1887         // gui_resize_shell(), so we have to manually set the rows and columns
1888         // here since MacVim doesn't change the rows and columns to avoid
1889         // inconsistent states between Vim and MacVim.  The message sent back
1890         // indicates that it is a reply to a message that originated in MacVim
1891         // since we need to be able to determine where a message originated.
1892         [self queueMessage:msgid data:d];
1894         gui_resize_shell(cols, rows);
1895     } else if (ExecuteMenuMsgID == msgid) {
1896         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1897         if (attrs) {
1898             NSArray *desc = [attrs objectForKey:@"descriptor"];
1899             vimmenu_T *menu = menu_for_descriptor(desc);
1900             if (menu)
1901                 gui_menu_cb(menu);
1902         }
1903     } else if (ToggleToolbarMsgID == msgid) {
1904         [self handleToggleToolbar];
1905     } else if (ScrollbarEventMsgID == msgid) {
1906         [self handleScrollbarEvent:data];
1907     } else if (SetFontMsgID == msgid) {
1908         [self handleSetFont:data];
1909     } else if (VimShouldCloseMsgID == msgid) {
1910         gui_shell_closed();
1911     } else if (DropFilesMsgID == msgid) {
1912         [self handleDropFiles:data];
1913     } else if (DropStringMsgID == msgid) {
1914         [self handleDropString:data];
1915     } else if (GotFocusMsgID == msgid) {
1916         if (!gui.in_focus)
1917             [self focusChange:YES];
1918     } else if (LostFocusMsgID == msgid) {
1919         if (gui.in_focus)
1920             [self focusChange:NO];
1921     } else if (SetMouseShapeMsgID == msgid) {
1922         const void *bytes = [data bytes];
1923         int shape = *((int*)bytes);  bytes += sizeof(int);
1924         update_mouseshape(shape);
1925     } else if (XcodeModMsgID == msgid) {
1926         [self handleXcodeMod:data];
1927     } else if (OpenWithArgumentsMsgID == msgid) {
1928         [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1929     } else if (FindReplaceMsgID == msgid) {
1930         [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1931     } else if (ActivatedImMsgID == msgid) {
1932         [self setImState:YES];
1933     } else if (DeactivatedImMsgID == msgid) {
1934         [self setImState:NO];
1935     } else if (NetBeansMsgID == msgid) {
1936 #ifdef FEAT_NETBEANS_INTG
1937         messageFromNetbeansMacVim();
1938 #endif
1939     } else if (SetMarkedTextMsgID == msgid) {
1940         [self handleMarkedText:data];
1941     } else if (ZoomMsgID == msgid) {
1942         if (!data) return;
1943         const void *bytes = [data bytes];
1944         int rows = *((int*)bytes);  bytes += sizeof(int);
1945         int cols = *((int*)bytes);  bytes += sizeof(int);
1946         //int zoom = *((int*)bytes);  bytes += sizeof(int);
1948         // NOTE: The frontend sends zoom messages here causing us to
1949         // immediately resize the shell and mirror the message back to the
1950         // frontend.  This is done to ensure that the draw commands reach the
1951         // frontend before the window actually changes size in order to avoid
1952         // flickering.  (Also see comment in SetTextDimensionsReplyMsgID
1953         // regarding resizing.)
1954         [self queueMessage:ZoomMsgID data:data];
1955         gui_resize_shell(cols, rows);
1956     } else {
1957         ASLogWarn(@"Unknown message received (msgid=%d)", msgid);
1958     }
1961 - (void)doKeyDown:(NSString *)key
1962           keyCode:(unsigned)code
1963         modifiers:(int)mods
1965     ASLogDebug(@"key='%@' code=%#x mods=%#x length=%d", key, code, mods,
1966             [key length]);
1967     if (!key) return;
1969     char_u *str = (char_u*)[key UTF8String];
1970     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1972     if ([self handleSpecialKey:key keyCode:code modifiers:mods])
1973         return;
1975 #ifdef FEAT_MBYTE
1976     char_u *conv_str = NULL;
1977     if (input_conv.vc_type != CONV_NONE) {
1978         conv_str = string_convert(&input_conv, str, &len);
1979         if (conv_str)
1980             str = conv_str;
1981     }
1982 #endif
1984     if (mods & MOD_MASK_CMD) {
1985         // NOTE: For normal input (non-special, 'macmeta' off) the modifier
1986         // flags are already included in the key event.  However, the Cmd key
1987         // flag is special and must always be added manually.
1988         // The Shift flag is already included in the key when the Command
1989         // key is held.  The same goes for Alt, unless Ctrl is held or
1990         // 'macmeta' is set.  It is important that these flags are cleared
1991         // _after_ special keys have been handled, since they should never be
1992         // cleared for special keys.
1993         mods &= ~MOD_MASK_SHIFT;
1994         if (!(mods & MOD_MASK_CTRL)) {
1995             BOOL mmta = curbuf ? curbuf->b_p_mmta : YES;
1996             if (!mmta)
1997                 mods &= ~MOD_MASK_ALT;
1998         }
2000         ASLogDebug(@"add mods=%#x", mods);
2001         char_u modChars[3] = { CSI, KS_MODIFIER, mods };
2002         add_to_input_buf(modChars, 3);
2003     } else if (mods & MOD_MASK_ALT && 1 == len && str[0] < 0x80
2004             && curbuf && curbuf->b_p_mmta) {
2005         // HACK! The 'macmeta' is set so we have to handle Alt key presses
2006         // separately.  Normally Alt key presses are interpreted by the
2007         // frontend but now we have to manually set the 8th bit and deal with
2008         // UTF-8 conversion.
2009         if ([self handleMacMetaKey:str[0] modifiers:mods])
2010             return;
2011     }
2014     for (i = 0; i < len; ++i) {
2015         ASLogDebug(@"add byte [%d/%d]: %#x", i, len, str[i]);
2016         add_to_input_buf(str+i, 1);
2017         if (CSI == str[i]) {
2018             // NOTE: If the converted string contains the byte CSI, then it
2019             // must be followed by the bytes KS_EXTRA, KE_CSI or things
2020             // won't work.
2021             static char_u extra[2] = { KS_EXTRA, KE_CSI };
2022             ASLogDebug(@"add KS_EXTRA, KE_CSI");
2023             add_to_input_buf(extra, 2);
2024         }
2025     }
2027 #ifdef FEAT_MBYTE
2028     if (conv_str)
2029         vim_free(conv_str);
2030 #endif
2033 - (BOOL)handleSpecialKey:(NSString *)key
2034                  keyCode:(unsigned)code
2035                modifiers:(int)mods
2037     int i;
2038     for (i = 0; special_keys[i].key_sym != 0; i++) {
2039         if (special_keys[i].key_sym == code) {
2040             ASLogDebug(@"Special key: %#x", code);
2041             break;
2042         }
2043     }
2044     if (special_keys[i].key_sym == 0)
2045         return NO;
2047     int ikey = special_keys[i].vim_code1 == NUL ? special_keys[i].vim_code0 :
2048             TO_SPECIAL(special_keys[i].vim_code0, special_keys[i].vim_code1);
2049     ikey = simplify_key(ikey, &mods);
2050     if (ikey == CSI)
2051         ikey = K_CSI;
2053     char_u chars[4];
2054     int len = 0;
2056     if (IS_SPECIAL(ikey)) {
2057         chars[0] = CSI;
2058         chars[1] = K_SECOND(ikey);
2059         chars[2] = K_THIRD(ikey);
2060         len = 3;
2061     } else if (mods & MOD_MASK_ALT && special_keys[i].vim_code1 == 0
2062 #ifdef FEAT_MBYTE
2063             && !enc_dbcs    // TODO: ?  (taken from gui_gtk_x11.c)
2064 #endif
2065             ) {
2066         ASLogDebug(@"Alt special=%d", ikey);
2068         // NOTE: The last entries in the special_keys struct when pressed
2069         // together with Alt need to be handled separately or they will not
2070         // work.
2071         // The following code was gleaned from gui_gtk_x11.c.
2072         mods &= ~MOD_MASK_ALT;
2073         int mkey = 0x80 | ikey;
2074 #ifdef FEAT_MBYTE
2075         if (enc_utf8) {  // TODO: What about other encodings?
2076             // Convert to utf-8
2077             chars[0] = (mkey >> 6) + 0xc0;
2078             chars[1] = mkey & 0xbf;
2079             if (chars[1] == CSI) {
2080                 // We end up here when ikey == ESC
2081                 chars[2] = KS_EXTRA;
2082                 chars[3] = KE_CSI;
2083                 len = 4;
2084             } else {
2085                 len = 2;
2086             }
2087         } else
2088 #endif
2089         {
2090             chars[0] = mkey;
2091             len = 1;
2092         }
2093     } else {
2094         ASLogDebug(@"Just ikey=%d", ikey);
2095         chars[0] = ikey;
2096         len = 1;
2097     }
2099     if (len > 0) {
2100         if (mods) {
2101             ASLogDebug(@"Adding mods to special: %d", mods);
2102             char_u modChars[3] = { CSI, KS_MODIFIER, (char_u)mods };
2103             add_to_input_buf(modChars, 3);
2104         }
2106         ASLogDebug(@"Adding special (%d): %x,%x,%x", len,
2107                 chars[0], chars[1], chars[2]);
2108         add_to_input_buf(chars, len);
2109     }
2111     return YES;
2114 - (BOOL)handleMacMetaKey:(int)ikey modifiers:(int)mods
2116     ASLogDebug(@"ikey=%d mods=%d", ikey, mods);
2118     // This code was taken from gui_w48.c and gui_gtk_x11.c.
2119     char_u string[7];
2120     int ch = simplify_key(ikey, &mods);
2122     // Remove the SHIFT modifier for keys where it's already included,
2123     // e.g., '(' and '*'
2124     if (ch < 0x100 && !isalpha(ch) && isprint(ch))
2125         mods &= ~MOD_MASK_SHIFT;
2127     // Interpret the ALT key as making the key META, include SHIFT, etc.
2128     ch = extract_modifiers(ch, &mods);
2129     if (ch == CSI)
2130         ch = K_CSI;
2132     int len = 0;
2133     if (mods) {
2134         string[len++] = CSI;
2135         string[len++] = KS_MODIFIER;
2136         string[len++] = mods;
2137     }
2139     string[len++] = ch;
2140 #ifdef FEAT_MBYTE
2141     // TODO: What if 'enc' is not "utf-8"?
2142     if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
2143         string[len++] = ch & 0xbf;
2144         string[len-2] = ((unsigned)ch >> 6) + 0xc0;
2145         if (string[len-1] == CSI) {
2146             string[len++] = KS_EXTRA;
2147             string[len++] = (int)KE_CSI;
2148         }
2149     }
2150 #endif
2152     add_to_input_buf(string, len);
2153     return YES;
2156 - (void)queueMessage:(int)msgid data:(NSData *)data
2158     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2159     if (data)
2160         [outputQueue addObject:data];
2161     else
2162         [outputQueue addObject:[NSData data]];
2165 - (void)connectionDidDie:(NSNotification *)notification
2167     // If the main connection to MacVim is lost this means that either MacVim
2168     // has crashed or this process did not receive its termination message
2169     // properly (e.g. if the TerminateNowMsgID was dropped).
2170     //
2171     // NOTE: This is not called if a Vim controller invalidates its connection.
2173     ASLogNotice(@"Main connection was lost before process had a chance "
2174                 "to terminate; preserving swap files.");
2175     getout_preserve_modified(1);
2178 - (void)blinkTimerFired:(NSTimer *)timer
2180     NSTimeInterval timeInterval = 0;
2182     [blinkTimer release];
2183     blinkTimer = nil;
2185     if (MMBlinkStateOn == blinkState) {
2186         gui_undraw_cursor();
2187         blinkState = MMBlinkStateOff;
2188         timeInterval = blinkOffInterval;
2189     } else if (MMBlinkStateOff == blinkState) {
2190         gui_update_cursor(TRUE, FALSE);
2191         blinkState = MMBlinkStateOn;
2192         timeInterval = blinkOnInterval;
2193     }
2195     if (timeInterval > 0) {
2196         blinkTimer = 
2197             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2198                                             selector:@selector(blinkTimerFired:)
2199                                             userInfo:nil repeats:NO] retain];
2200         [self flushQueue:YES];
2201     }
2204 - (void)focusChange:(BOOL)on
2206     gui_focus_change(on);
2209 - (void)handleToggleToolbar
2211     // If 'go' contains 'T', then remove it, else add it.
2213     char_u go[sizeof(GO_ALL)+2];
2214     char_u *p;
2215     int len;
2217     STRCPY(go, p_go);
2218     p = vim_strchr(go, GO_TOOLBAR);
2219     len = STRLEN(go);
2221     if (p != NULL) {
2222         char_u *end = go + len;
2223         while (p < end) {
2224             p[0] = p[1];
2225             ++p;
2226         }
2227     } else {
2228         go[len] = GO_TOOLBAR;
2229         go[len+1] = NUL;
2230     }
2232     set_option_value((char_u*)"guioptions", 0, go, 0);
2235 - (void)handleScrollbarEvent:(NSData *)data
2237     if (!data) return;
2239     const void *bytes = [data bytes];
2240     int32_t ident = *((int32_t*)bytes);  bytes += sizeof(int32_t);
2241     int hitPart = *((int*)bytes);  bytes += sizeof(int);
2242     float fval = *((float*)bytes);  bytes += sizeof(float);
2243     scrollbar_T *sb = gui_find_scrollbar(ident);
2245     if (sb) {
2246         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2247         long value = sb_info->value;
2248         long size = sb_info->size;
2249         long max = sb_info->max;
2250         BOOL isStillDragging = NO;
2251         BOOL updateKnob = YES;
2253         switch (hitPart) {
2254         case NSScrollerDecrementPage:
2255             value -= (size > 2 ? size - 2 : 1);
2256             break;
2257         case NSScrollerIncrementPage:
2258             value += (size > 2 ? size - 2 : 1);
2259             break;
2260         case NSScrollerDecrementLine:
2261             --value;
2262             break;
2263         case NSScrollerIncrementLine:
2264             ++value;
2265             break;
2266         case NSScrollerKnob:
2267             isStillDragging = YES;
2268             // fall through ...
2269         case NSScrollerKnobSlot:
2270             value = (long)(fval * (max - size + 1));
2271             // fall through ...
2272         default:
2273             updateKnob = NO;
2274             break;
2275         }
2277         gui_drag_scrollbar(sb, value, isStillDragging);
2279         if (updateKnob) {
2280             // Dragging the knob or option+clicking automatically updates
2281             // the knob position (on the actual NSScroller), so we only
2282             // need to set the knob position in the other cases.
2283             if (sb->wp) {
2284                 // Update both the left&right vertical scrollbars.
2285                 int32_t idL = (int32_t)sb->wp->w_scrollbars[SBAR_LEFT].ident;
2286                 int32_t idR = (int32_t)sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2287                 [self setScrollbarThumbValue:value size:size max:max
2288                                   identifier:idL];
2289                 [self setScrollbarThumbValue:value size:size max:max
2290                                   identifier:idR];
2291             } else {
2292                 // Update the horizontal scrollbar.
2293                 [self setScrollbarThumbValue:value size:size max:max
2294                                   identifier:ident];
2295             }
2296         }
2297     }
2300 - (void)handleSetFont:(NSData *)data
2302     if (!data) return;
2304     const void *bytes = [data bytes];
2305     int pointSize = (int)*((float*)bytes);  bytes += sizeof(float);
2307     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2308     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2309     bytes += len;
2311     [name appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2312     char_u *s = (char_u*)[name UTF8String];
2314     unsigned wlen = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2315     char_u *ws = NULL;
2316     if (wlen > 0) {
2317         NSMutableString *wname = [NSMutableString stringWithUTF8String:bytes];
2318         bytes += wlen;
2320         [wname appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2321         ws = (char_u*)[wname UTF8String];
2322     }
2324 #ifdef FEAT_MBYTE
2325     s = CONVERT_FROM_UTF8(s);
2326     if (ws) {
2327         ws = CONVERT_FROM_UTF8(ws);
2328     }
2329 #endif
2331     set_option_value((char_u*)"guifont", 0, s, 0);
2333     if (ws && gui.wide_font != NOFONT) {
2334         // NOTE: This message is sent on Cmd-+/Cmd-- and as such should only
2335         // change the wide font if 'gfw' is non-empty (the frontend always has
2336         // some wide font set, even if 'gfw' is empty).
2337         set_option_value((char_u*)"guifontwide", 0, ws, 0);
2338     }
2340 #ifdef FEAT_MBYTE
2341     if (ws) {
2342         CONVERT_FROM_UTF8_FREE(ws);
2343     }
2344     CONVERT_FROM_UTF8_FREE(s);
2345 #endif
2347     [self redrawScreen];
2350 - (void)handleDropFiles:(NSData *)data
2352     // TODO: Get rid of this method; instead use Vim script directly.  At the
2353     // moment I know how to do this to open files in tabs, but I'm not sure how
2354     // to add the filenames to the command line when in command line mode.
2356     if (!data) return;
2358     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2359     if (!args) return;
2361     id obj = [args objectForKey:@"forceOpen"];
2362     BOOL forceOpen = YES;
2363     if (obj)
2364         forceOpen = [obj boolValue];
2366     NSArray *filenames = [args objectForKey:@"filenames"];
2367     if (!(filenames && [filenames count] > 0)) return;
2369 #ifdef FEAT_DND
2370     if (!forceOpen && (State & CMDLINE)) {
2371         // HACK!  If Vim is in command line mode then the files names
2372         // should be added to the command line, instead of opening the
2373         // files in tabs (unless forceOpen is set).  This is taken care of by
2374         // gui_handle_drop().
2375         int n = [filenames count];
2376         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2377         if (fnames) {
2378             int i = 0;
2379             for (i = 0; i < n; ++i)
2380                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2382             // NOTE!  This function will free 'fnames'.
2383             // HACK!  It is assumed that the 'x' and 'y' arguments are
2384             // unused when in command line mode.
2385             gui_handle_drop(0, 0, 0, fnames, n);
2386         }
2387     } else
2388 #endif // FEAT_DND
2389     {
2390         [self handleOpenWithArguments:args];
2391     }
2394 - (void)handleDropString:(NSData *)data
2396     if (!data) return;
2398 #ifdef FEAT_DND
2399     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2400     const void *bytes = [data bytes];
2401     int len = *((int*)bytes);  bytes += sizeof(int);
2402     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2404     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2405     NSRange range = { 0, [string length] };
2406     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2407                                          withString:@"\x0a" options:0
2408                                               range:range];
2409     if (0 == n) {
2410         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2411                                        options:0 range:range];
2412     }
2414     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2415     char_u *s = (char_u*)[string UTF8String];
2416 #ifdef FEAT_MBYTE
2417     if (input_conv.vc_type != CONV_NONE)
2418         s = string_convert(&input_conv, s, &len);
2419 #endif
2420     dnd_yank_drag_data(s, len);
2421 #ifdef FEAT_MBYTE
2422     if (input_conv.vc_type != CONV_NONE)
2423         vim_free(s);
2424 #endif
2425     add_to_input_buf(dropkey, sizeof(dropkey));
2426 #endif // FEAT_DND
2429 - (void)startOdbEditWithArguments:(NSDictionary *)args
2431 #ifdef FEAT_ODB_EDITOR
2432     id obj = [args objectForKey:@"remoteID"];
2433     if (!obj) return;
2435     OSType serverID = [obj unsignedIntValue];
2436     NSString *remotePath = [args objectForKey:@"remotePath"];
2438     NSAppleEventDescriptor *token = nil;
2439     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2440     obj = [args objectForKey:@"remoteTokenDescType"];
2441     if (tokenData && obj) {
2442         DescType tokenType = [obj unsignedLongValue];
2443         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2444                                                                 data:tokenData];
2445     }
2447     NSArray *filenames = [args objectForKey:@"filenames"];
2448     unsigned i, numFiles = [filenames count];
2449     for (i = 0; i < numFiles; ++i) {
2450         NSString *filename = [filenames objectAtIndex:i];
2451         char_u *s = [filename vimStringSave];
2452         buf_T *buf = buflist_findname(s);
2453         vim_free(s);
2455         if (buf) {
2456             if (buf->b_odb_token) {
2457                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2458                 buf->b_odb_token = NULL;
2459             }
2461             if (buf->b_odb_fname) {
2462                 vim_free(buf->b_odb_fname);
2463                 buf->b_odb_fname = NULL;
2464             }
2466             buf->b_odb_server_id = serverID;
2468             if (token)
2469                 buf->b_odb_token = [token retain];
2470             if (remotePath)
2471                 buf->b_odb_fname = [remotePath vimStringSave];
2472         } else {
2473             ASLogWarn(@"Could not find buffer '%@' for ODB editing.", filename);
2474         }
2475     }
2476 #endif // FEAT_ODB_EDITOR
2479 - (void)handleXcodeMod:(NSData *)data
2481 #if 0
2482     const void *bytes = [data bytes];
2483     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2484     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2485     if (0 == len)
2486         return;
2488     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2489             descriptorWithDescriptorType:type
2490                                    bytes:bytes
2491                                   length:len];
2492 #endif
2495 - (void)handleOpenWithArguments:(NSDictionary *)args
2497     // ARGUMENT:                DESCRIPTION:
2498     // -------------------------------------------------------------
2499     // filenames                list of filenames
2500     // dontOpen                 don't open files specified in above argument
2501     // layout                   which layout to use to open files
2502     // selectionRange           range of lines to select
2503     // searchText               string to search for
2504     // cursorLine               line to position the cursor on
2505     // cursorColumn             column to position the cursor on
2506     //                          (only valid when "cursorLine" is set)
2507     // remoteID                 ODB parameter
2508     // remotePath               ODB parameter
2509     // remoteTokenDescType      ODB parameter
2510     // remoteTokenData          ODB parameter
2512     ASLogDebug(@"args=%@ (starting=%d)", args, starting);
2514     NSArray *filenames = [args objectForKey:@"filenames"];
2515     int i, numFiles = filenames ? [filenames count] : 0;
2516     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2517     int layout = [[args objectForKey:@"layout"] intValue];
2519     // Change to directory of first file to open if this is an "unused" editor
2520     // (but do not do this if editing remotely).
2521     if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2522             && (starting || [self unusedEditor]) ) {
2523         char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2524         if (mch_isdir(s)) {
2525             mch_chdir((char*)s);
2526         } else {
2527             vim_chdirfile(s);
2528         }
2529         vim_free(s);
2530     }
2532     if (starting > 0) {
2533         // When Vim is starting we simply add the files to be opened to the
2534         // global arglist and Vim will take care of opening them for us.
2535         if (openFiles && numFiles > 0) {
2536             for (i = 0; i < numFiles; i++) {
2537                 NSString *fname = [filenames objectAtIndex:i];
2538                 char_u *p = NULL;
2540                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2541                         || (p = [fname vimStringSave]) == NULL)
2542                     exit(2); // See comment in -[MMBackend exit]
2543                 else
2544                     alist_add(&global_alist, p, 2);
2545             }
2547             // Vim will take care of arranging the files added to the arglist
2548             // in windows or tabs; all we must do is to specify which layout to
2549             // use.
2550             initialWindowLayout = layout;
2551         }
2552     } else {
2553         // When Vim is already open we resort to some trickery to open the
2554         // files with the specified layout.
2555         //
2556         // TODO: Figure out a better way to handle this?
2557         if (openFiles && numFiles > 0) {
2558             BOOL oneWindowInTab = topframe ? YES
2559                                            : (topframe->fr_layout == FR_LEAF);
2560             BOOL bufChanged = NO;
2561             BOOL bufHasFilename = NO;
2562             if (curbuf) {
2563                 bufChanged = curbufIsChanged();
2564                 bufHasFilename = curbuf->b_ffname != NULL;
2565             }
2567             // Temporarily disable flushing since the following code may
2568             // potentially cause multiple redraws.
2569             flushDisabled = YES;
2571             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2572             if (WIN_TABS == layout && !onlyOneTab) {
2573                 // By going to the last tabpage we ensure that the new tabs
2574                 // will appear last (if this call is left out, the taborder
2575                 // becomes messy).
2576                 goto_tabpage(9999);
2577             }
2579             // Make sure we're in normal mode first.
2580             [self addInput:@"<C-\\><C-N>"];
2582             if (numFiles > 1) {
2583                 // With "split layout" we open a new tab before opening
2584                 // multiple files if the current tab has more than one window
2585                 // or if there is exactly one window but whose buffer has a
2586                 // filename.  (The :drop command ensures modified buffers get
2587                 // their own window.)
2588                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2589                         (!oneWindowInTab || bufHasFilename))
2590                     [self addInput:@":tabnew<CR>"];
2592                 // The files are opened by constructing a ":drop ..." command
2593                 // and executing it.
2594                 NSMutableString *cmd = (WIN_TABS == layout)
2595                         ? [NSMutableString stringWithString:@":tab drop"]
2596                         : [NSMutableString stringWithString:@":drop"];
2598                 for (i = 0; i < numFiles; ++i) {
2599                     NSString *file = [filenames objectAtIndex:i];
2600                     file = [file stringByEscapingSpecialFilenameCharacters];
2601                     [cmd appendString:@" "];
2602                     [cmd appendString:file];
2603                 }
2605                 // Temporarily clear 'suffixes' so that the files are opened in
2606                 // the same order as they appear in the "filenames" array.
2607                 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2609                 [self addInput:cmd];
2611                 // Split the view into multiple windows if requested.
2612                 if (WIN_HOR == layout)
2613                     [self addInput:@"|sall"];
2614                 else if (WIN_VER == layout)
2615                     [self addInput:@"|vert sall"];
2617                 // Restore the old value of 'suffixes'.
2618                 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2619             } else {
2620                 // When opening one file we try to reuse the current window,
2621                 // but not if its buffer is modified or has a filename.
2622                 // However, the 'arglist' layout always opens the file in the
2623                 // current window.
2624                 NSString *file = [[filenames lastObject]
2625                         stringByEscapingSpecialFilenameCharacters];
2626                 NSString *cmd;
2627                 if (WIN_HOR == layout) {
2628                     if (!(bufHasFilename || bufChanged))
2629                         cmd = [NSString stringWithFormat:@":e %@", file];
2630                     else
2631                         cmd = [NSString stringWithFormat:@":sp %@", file];
2632                 } else if (WIN_VER == layout) {
2633                     if (!(bufHasFilename || bufChanged))
2634                         cmd = [NSString stringWithFormat:@":e %@", file];
2635                     else
2636                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2637                 } else if (WIN_TABS == layout) {
2638                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2639                         cmd = [NSString stringWithFormat:@":e %@", file];
2640                     else
2641                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2642                 } else {
2643                     // (The :drop command will split if there is a modified
2644                     // buffer.)
2645                     cmd = [NSString stringWithFormat:@":drop %@", file];
2646                 }
2648                 [self addInput:cmd];
2649                 [self addInput:@"<CR>"];
2650             }
2652             // Force screen redraw (does it have to be this complicated?).
2653             // (This code was taken from the end of gui_handle_drop().)
2654             update_screen(NOT_VALID);
2655             setcursor();
2656             out_flush();
2657             gui_update_cursor(FALSE, FALSE);
2658             maketitle();
2660             flushDisabled = NO;
2661         }
2662     }
2664     if ([args objectForKey:@"remoteID"]) {
2665         // NOTE: We have to delay processing any ODB related arguments since
2666         // the file(s) may not be opened until the input buffer is processed.
2667         [self performSelector:@selector(startOdbEditWithArguments:)
2668                    withObject:args
2669                    afterDelay:0];
2670     }
2672     NSString *lineString = [args objectForKey:@"cursorLine"];
2673     if (lineString && [lineString intValue] > 0) {
2674         NSString *columnString = [args objectForKey:@"cursorColumn"];
2675         if (!(columnString && [columnString intValue] > 0))
2676             columnString = @"1";
2678         NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2679                 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2680         [self addInput:cmd];
2681     }
2683     NSString *rangeString = [args objectForKey:@"selectionRange"];
2684     if (rangeString) {
2685         // Build a command line string that will select the given range of
2686         // lines.  If range.length == 0, then position the cursor on the given
2687         // line but do not select.
2688         NSRange range = NSRangeFromString(rangeString);
2689         NSString *cmd;
2690         if (range.length > 0) {
2691             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2692                     NSMaxRange(range), range.location];
2693         } else {
2694             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2695                     range.location];
2696         }
2698         [self addInput:cmd];
2699     }
2701     NSString *searchText = [args objectForKey:@"searchText"];
2702     if (searchText) {
2703         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2704                 searchText]];
2705     }
2708 - (BOOL)checkForModifiedBuffers
2710     buf_T *buf;
2711     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2712         if (bufIsChanged(buf)) {
2713             return YES;
2714         }
2715     }
2717     return NO;
2720 - (void)addInput:(NSString *)input
2722     // NOTE: This code is essentially identical to server_to_input_buf(),
2723     // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2724     char_u *string = [input vimStringSave];
2725     if (!string) return;
2727     /* Set 'cpoptions' the way we want it.
2728      *    B set - backslashes are *not* treated specially
2729      *    k set - keycodes are *not* reverse-engineered
2730      *    < unset - <Key> sequences *are* interpreted
2731      *  The last but one parameter of replace_termcodes() is TRUE so that the
2732      *  <lt> sequence is recognised - needed for a real backslash.
2733      */
2734     char_u *ptr = NULL;
2735     char_u *cpo_save = p_cpo;
2736     p_cpo = (char_u *)"Bk";
2737     char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2738     p_cpo = cpo_save;
2740     if (*ptr != NUL)    /* trailing CTRL-V results in nothing */
2741     {
2742         /*
2743          * Add the string to the input stream.
2744          * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2745          *
2746          * First clear typed characters from the typeahead buffer, there could
2747          * be half a mapping there.  Then append to the existing string, so
2748          * that multiple commands from a client are concatenated.
2749          */
2750         if (typebuf.tb_maplen < typebuf.tb_len)
2751             del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2752         (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2754         /* Let input_available() know we inserted text in the typeahead
2755          * buffer. */
2756         typebuf_was_filled = TRUE;
2757     }
2758     vim_free(ptr);
2759     vim_free(string);
2762 - (BOOL)unusedEditor
2764     BOOL oneWindowInTab = topframe ? YES
2765                                    : (topframe->fr_layout == FR_LEAF);
2766     BOOL bufChanged = NO;
2767     BOOL bufHasFilename = NO;
2768     if (curbuf) {
2769         bufChanged = curbufIsChanged();
2770         bufHasFilename = curbuf->b_ffname != NULL;
2771     }
2773     BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2775     return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2778 - (void)redrawScreen
2780     // Force screen redraw (does it have to be this complicated?).
2781     redraw_all_later(CLEAR);
2782     update_screen(NOT_VALID);
2783     setcursor();
2784     out_flush();
2785     gui_update_cursor(FALSE, FALSE);
2787     // HACK! The cursor is not put back at the command line by the above
2788     // "redraw commands".  The following test seems to do the trick though.
2789     if (State & CMDLINE)
2790         redrawcmdline();
2793 - (void)handleFindReplace:(NSDictionary *)args
2795     if (!args) return;
2797     NSString *findString = [args objectForKey:@"find"];
2798     if (!findString) return;
2800     char_u *find = [findString vimStringSave];
2801     char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2802     int flags = [[args objectForKey:@"flags"] intValue];
2804     // NOTE: The flag 0x100 is used to indicate a backward search.
2805     gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2807     vim_free(find);
2808     vim_free(replace);
2812 - (void)handleMarkedText:(NSData *)data
2814     const void *bytes = [data bytes];
2815     unsigned textlen = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2816     int32_t pos = *((int32_t*)bytes);  bytes += sizeof(int32_t);
2817     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2818     char *chars = (char *)bytes;
2820     ASLogDebug(@"textlen=%d pos=%d len=%d chars=%s", textlen, pos, len, chars);
2822     if (pos < 0) {
2823         im_preedit_abandon_macvim();
2824     } else if (textllen == 0) {
2825         im_preedit_end_macvim();
2826     } else {
2827         if (!preedit_get_status())
2828             im_preedit_start_macvim();
2830         im_preedit_changed_macvim(chars, pos + len);
2831     }
2834 @end // MMBackend (Private)
2839 @implementation MMBackend (ClientServer)
2841 - (NSString *)connectionNameFromServerName:(NSString *)name
2843     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2845     return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2846         lowercaseString];
2849 - (NSConnection *)connectionForServerName:(NSString *)name
2851     // TODO: Try 'name%d' if 'name' fails.
2852     NSString *connName = [self connectionNameFromServerName:name];
2853     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2855     if (!svrConn) {
2856         svrConn = [NSConnection connectionWithRegisteredName:connName
2857                                                            host:nil];
2858         // Try alternate server...
2859         if (!svrConn && alternateServerName) {
2860             ASLogInfo(@"  trying to connect to alternate server: %@",
2861                       alternateServerName);
2862             connName = [self connectionNameFromServerName:alternateServerName];
2863             svrConn = [NSConnection connectionWithRegisteredName:connName
2864                                                             host:nil];
2865         }
2867         // Try looking for alternate servers...
2868         if (!svrConn) {
2869             ASLogInfo(@"  looking for alternate servers...");
2870             NSString *alt = [self alternateServerNameForName:name];
2871             if (alt != alternateServerName) {
2872                 ASLogInfo(@"  found alternate server: %@", alt);
2873                 [alternateServerName release];
2874                 alternateServerName = [alt copy];
2875             }
2876         }
2878         // Try alternate server again...
2879         if (!svrConn && alternateServerName) {
2880             ASLogInfo(@"  trying to connect to alternate server: %@",
2881                       alternateServerName);
2882             connName = [self connectionNameFromServerName:alternateServerName];
2883             svrConn = [NSConnection connectionWithRegisteredName:connName
2884                                                             host:nil];
2885         }
2887         if (svrConn) {
2888             [connectionNameDict setObject:svrConn forKey:connName];
2890             ASLogDebug(@"Adding %@ as connection observer for %@",
2891                        self, svrConn);
2892             [[NSNotificationCenter defaultCenter] addObserver:self
2893                     selector:@selector(serverConnectionDidDie:)
2894                         name:NSConnectionDidDieNotification object:svrConn];
2895         }
2896     }
2898     return svrConn;
2901 - (NSConnection *)connectionForServerPort:(int)port
2903     NSConnection *conn;
2904     NSEnumerator *e = [connectionNameDict objectEnumerator];
2906     while ((conn = [e nextObject])) {
2907         // HACK! Assume connection uses mach ports.
2908         if (port == [(NSMachPort*)[conn sendPort] machPort])
2909             return conn;
2910     }
2912     return nil;
2915 - (void)serverConnectionDidDie:(NSNotification *)notification
2917     ASLogDebug(@"notification=%@", notification);
2919     NSConnection *svrConn = [notification object];
2921     ASLogDebug(@"Removing %@ as connection observer from %@", self, svrConn);
2922     [[NSNotificationCenter defaultCenter]
2923             removeObserver:self
2924                       name:NSConnectionDidDieNotification
2925                     object:svrConn];
2927     [connectionNameDict removeObjectsForKeys:
2928         [connectionNameDict allKeysForObject:svrConn]];
2930     // HACK! Assume connection uses mach ports.
2931     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2932     NSNumber *key = [NSNumber numberWithInt:port];
2934     [clientProxyDict removeObjectForKey:key];
2935     [serverReplyDict removeObjectForKey:key];
2938 - (void)addClient:(NSDistantObject *)client
2940     NSConnection *conn = [client connectionForProxy];
2941     // HACK! Assume connection uses mach ports.
2942     int port = [(NSMachPort*)[conn sendPort] machPort];
2943     NSNumber *key = [NSNumber numberWithInt:port];
2945     if (![clientProxyDict objectForKey:key]) {
2946         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2947         [clientProxyDict setObject:client forKey:key];
2948     }
2950     // NOTE: 'clientWindow' is a global variable which is used by <client>
2951     clientWindow = port;
2954 - (NSString *)alternateServerNameForName:(NSString *)name
2956     if (!(name && [name length] > 0))
2957         return nil;
2959     // Only look for alternates if 'name' doesn't end in a digit.
2960     unichar lastChar = [name characterAtIndex:[name length]-1];
2961     if (lastChar >= '0' && lastChar <= '9')
2962         return nil;
2964     // Look for alternates among all current servers.
2965     NSArray *list = [self serverList];
2966     if (!(list && [list count] > 0))
2967         return nil;
2969     // Filter out servers starting with 'name' and ending with a number. The
2970     // (?i) pattern ensures that the match is case insensitive.
2971     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2972     NSPredicate *pred = [NSPredicate predicateWithFormat:
2973             @"SELF MATCHES %@", pat];
2974     list = [list filteredArrayUsingPredicate:pred];
2975     if ([list count] > 0) {
2976         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2977         return [list objectAtIndex:0];
2978     }
2980     return nil;
2983 @end // MMBackend (ClientServer)
2988 @implementation NSString (MMServerNameCompare)
2989 - (NSComparisonResult)serverNameCompare:(NSString *)string
2991     return [self compare:string
2992                  options:NSCaseInsensitiveSearch|NSNumericSearch];
2994 @end
2999 static int eventModifierFlagsToVimModMask(int modifierFlags)
3001     int modMask = 0;
3003     if (modifierFlags & NSShiftKeyMask)
3004         modMask |= MOD_MASK_SHIFT;
3005     if (modifierFlags & NSControlKeyMask)
3006         modMask |= MOD_MASK_CTRL;
3007     if (modifierFlags & NSAlternateKeyMask)
3008         modMask |= MOD_MASK_ALT;
3009     if (modifierFlags & NSCommandKeyMask)
3010         modMask |= MOD_MASK_CMD;
3012     return modMask;
3015 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
3017     int modMask = 0;
3019     if (modifierFlags & NSShiftKeyMask)
3020         modMask |= MOUSE_SHIFT;
3021     if (modifierFlags & NSControlKeyMask)
3022         modMask |= MOUSE_CTRL;
3023     if (modifierFlags & NSAlternateKeyMask)
3024         modMask |= MOUSE_ALT;
3026     return modMask;
3029 static int eventButtonNumberToVimMouseButton(int buttonNumber)
3031     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
3033     return (buttonNumber >= 0 && buttonNumber < 3)
3034             ? mouseButton[buttonNumber] : -1;
3039 // This function is modeled after the VimToPython function found in if_python.c
3040 // NB This does a deep copy by value, it does not lookup references like the
3041 // VimToPython function does.  This is because I didn't want to deal with the
3042 // retain cycles that this would create, and we can cover 99% of the use cases
3043 // by ignoring it.  If we ever switch to using GC in MacVim then this
3044 // functionality can be implemented easily.
3045 static id vimToCocoa(typval_T * tv, int depth)
3047     id result = nil;
3048     id newObj = nil;
3051     // Avoid infinite recursion
3052     if (depth > 100) {
3053         return nil;
3054     }
3056     if (tv->v_type == VAR_STRING) {
3057         char_u * val = tv->vval.v_string;
3058         // val can be NULL if the string is empty
3059         if (!val) {
3060             result = [NSString string];
3061         } else {
3062 #ifdef FEAT_MBYTE
3063             val = CONVERT_TO_UTF8(val);
3064 #endif
3065             result = [NSString stringWithUTF8String:(char*)val];
3066 #ifdef FEAT_MBYTE
3067             CONVERT_TO_UTF8_FREE(val);
3068 #endif
3069         }
3070     } else if (tv->v_type == VAR_NUMBER) {
3071         // looks like sizeof(varnumber_T) is always <= sizeof(long)
3072         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
3073     } else if (tv->v_type == VAR_LIST) {
3074         list_T * list = tv->vval.v_list;
3075         listitem_T * curr;
3077         NSMutableArray * arr = result = [NSMutableArray array];
3079         if (list != NULL) {
3080             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
3081                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
3082                 [arr addObject:newObj];
3083             }
3084         }
3085     } else if (tv->v_type == VAR_DICT) {
3086         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
3088         if (tv->vval.v_dict != NULL) {
3089             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
3090             int todo = ht->ht_used;
3091             hashitem_T * hi;
3092             dictitem_T * di;
3094             for (hi = ht->ht_array; todo > 0; ++hi) {
3095                 if (!HASHITEM_EMPTY(hi)) {
3096                     --todo;
3098                     di = dict_lookup(hi);
3099                     newObj = vimToCocoa(&di->di_tv, depth + 1);
3101                     char_u * keyval = hi->hi_key;
3102 #ifdef FEAT_MBYTE
3103                     keyval = CONVERT_TO_UTF8(keyval);
3104 #endif
3105                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
3106 #ifdef FEAT_MBYTE
3107                     CONVERT_TO_UTF8_FREE(keyval);
3108 #endif
3109                     [dict setObject:newObj forKey:key];
3110                 }
3111             }
3112         }
3113     } else { // only func refs should fall into this category?
3114         result = nil;
3115     }
3117     return result;
3121 // This function is modeled after eval_client_expr_to_string found in main.c
3122 // Returns nil if there was an error evaluating the expression, and writes a
3123 // message to errorStr.
3124 // TODO Get the error that occurred while evaluating the expression in vim
3125 // somehow.
3126 static id evalExprCocoa(NSString * expr, NSString ** errstr)
3129     char_u *s = (char_u*)[expr UTF8String];
3131 #ifdef FEAT_MBYTE
3132     s = CONVERT_FROM_UTF8(s);
3133 #endif
3135     int save_dbl = debug_break_level;
3136     int save_ro = redir_off;
3138     debug_break_level = -1;
3139     redir_off = 0;
3140     ++emsg_skip;
3142     typval_T * tvres = eval_expr(s, NULL);
3144     debug_break_level = save_dbl;
3145     redir_off = save_ro;
3146     --emsg_skip;
3148     setcursor();
3149     out_flush();
3151 #ifdef FEAT_MBYTE
3152     CONVERT_FROM_UTF8_FREE(s);
3153 #endif
3155 #ifdef FEAT_GUI
3156     if (gui.in_use)
3157         gui_update_cursor(FALSE, FALSE);
3158 #endif
3160     if (tvres == NULL) {
3161         free_tv(tvres);
3162         *errstr = @"Expression evaluation failed.";
3163     }
3165     id res = vimToCocoa(tvres, 1);
3167     free_tv(tvres);
3169     if (res == nil) {
3170         *errstr = @"Conversion to cocoa values failed.";
3171     }
3173     return res;
3178 @implementation NSString (VimStrings)
3180 + (id)stringWithVimString:(char_u *)s
3182     // This method ensures a non-nil string is returned.  If 's' cannot be
3183     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
3184     // still fails an empty NSString is returned.
3185     NSString *string = nil;
3186     if (s) {
3187 #ifdef FEAT_MBYTE
3188         s = CONVERT_TO_UTF8(s);
3189 #endif
3190         string = [NSString stringWithUTF8String:(char*)s];
3191         if (!string) {
3192             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
3193             // latin-1?
3194             string = [NSString stringWithCString:(char*)s
3195                                         encoding:NSISOLatin1StringEncoding];
3196         }
3197 #ifdef FEAT_MBYTE
3198         CONVERT_TO_UTF8_FREE(s);
3199 #endif
3200     }
3202     return string != nil ? string : [NSString string];
3205 - (char_u *)vimStringSave
3207     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3209 #ifdef FEAT_MBYTE
3210     s = CONVERT_FROM_UTF8(s);
3211 #endif
3212     ret = vim_strsave(s);
3213 #ifdef FEAT_MBYTE
3214     CONVERT_FROM_UTF8_FREE(s);
3215 #endif
3217     return ret;
3220 @end // NSString (VimStrings)