Add support for :winpos
[MacVim.git] / src / MacVim / MMBackend.m
blobbf5b58a8ba6be9b299d33fb8ad19a753d6ff49d8
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)getWindowPositionX:(int*)x Y:(int*)y
331     // NOTE: winposX and winposY are set by the SetWindowPositionMsgID message.
332     if (x) *x = winposX;
333     if (y) *y = winposY;
336 - (void)setWindowPositionX:(int)x Y:(int)y
338     // NOTE: Setting the window position has no immediate effect on the cached
339     // variables winposX and winposY.  These are set by the frontend when the
340     // window actually moves (see SetWindowPositionMsgID).
341     ASLogDebug(@"x=%d y=%d", x, y);
342     int pos[2] = { x, y };
343     NSData *data = [NSData dataWithBytes:pos length:2*sizeof(int)];
344     [self queueMessage:SetWindowPositionMsgID data:data];
347 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
349     [self queueMessage:msgid data:[props dictionaryAsData]];
352 - (BOOL)checkin
354     if (![self connection]) {
355         if (waitForAck) {
356             // This is a preloaded process and as such should not cause the
357             // MacVim to be opened.  We probably got here as a result of the
358             // user quitting MacVim while the process was preloading, so exit
359             // this process too.
360             // (Don't use mch_exit() since it assumes the process has properly
361             // started.)
362             exit(0);
363         }
365         NSBundle *mainBundle = [NSBundle mainBundle];
366 #if 0
367         OSStatus status;
368         FSRef ref;
370         // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
371         // the API to pass Apple Event parameters is broken on 10.4).
372         NSString *path = [mainBundle bundlePath];
373         status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
374         if (noErr == status) {
375             // Pass parameter to the 'Open' Apple Event that tells MacVim not
376             // to open an untitled window.
377             NSAppleEventDescriptor *desc =
378                     [NSAppleEventDescriptor recordDescriptor];
379             [desc setParamDescriptor:
380                     [NSAppleEventDescriptor descriptorWithBoolean:NO]
381                           forKeyword:keyMMUntitledWindow];
383             LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
384                     kLSLaunchDefaults, NULL };
385             status = LSOpenFromRefSpec(&spec, NULL);
386         }
388         if (noErr != status) {
389         ASLogCrit(@"Failed to launch MacVim (path=%@).%@",
390                   path, MMSymlinkWarningString);
391             return NO;
392         }
393 #else
394         // Launch MacVim using NSTask.  For some reason the above code using
395         // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
396         // fails, the dock icon starts bouncing and never stops).  It seems
397         // like rebuilding the Launch Services database takes care of this
398         // problem, but the NSTask way seems more stable so stick with it.
399         //
400         // NOTE!  Using NSTask to launch the GUI has the negative side-effect
401         // that the GUI won't be activated (or raised) so there is a hack in
402         // MMAppController which raises the app when a new window is opened.
403         NSMutableArray *args = [NSMutableArray arrayWithObjects:
404             [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
405         NSString *exeName = [[mainBundle infoDictionary]
406                 objectForKey:@"CFBundleExecutable"];
407         NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
408         if (!path) {
409             ASLogCrit(@"Could not find MacVim executable in bundle.%@",
410                       MMSymlinkWarningString);
411             return NO;
412         }
414         [NSTask launchedTaskWithLaunchPath:path arguments:args];
415 #endif
417         // HACK!  Poll the mach bootstrap server until it returns a valid
418         // connection to detect that MacVim has finished launching.  Also set a
419         // time-out date so that we don't get stuck doing this forever.
420         NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
421         while (![self connection] &&
422                 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
423             [[NSRunLoop currentRunLoop]
424                     runMode:NSDefaultRunLoopMode
425                  beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
427         // NOTE: [self connection] will set 'connection' as a side-effect.
428         if (!connection) {
429             ASLogCrit(@"Timed-out waiting for GUI to launch.");
430             return NO;
431         }
432     }
434     @try {
435         [[NSNotificationCenter defaultCenter] addObserver:self
436                 selector:@selector(connectionDidDie:)
437                     name:NSConnectionDidDieNotification object:connection];
439         appProxy = [[connection rootProxy] retain];
440         [appProxy setProtocolForProxy:@protocol(MMAppProtocol)];
442         // NOTE: We do not set any new timeout values for the connection to the
443         // frontend.  This means that if the frontend is "stuck" (e.g. in a
444         // modal loop) then any calls to the frontend will block indefinitely
445         // (the default timeouts are huge).
447         int pid = [[NSProcessInfo processInfo] processIdentifier];
449         identifier = [appProxy connectBackend:self pid:pid];
450         return YES;
451     }
452     @catch (NSException *ex) {
453         ASLogDebug(@"Connect backend failed: reason=%@", ex);
454     }
456     return NO;
459 - (BOOL)openGUIWindow
461     if (gui_win_x != -1 && gui_win_y != -1) {
462         // NOTE: the gui_win_* coordinates are both set to -1 if no :winpos
463         // command is in .[g]vimrc.  (This way of detecting if :winpos has been
464         // used may cause problems if a second monitor is located to the left
465         // and underneath the main monitor as it will have negative
466         // coordinates.  However, this seems like a minor problem that is not
467         // worth fixing since all GUIs work this way.)
468         ASLogDebug(@"default x=%d y=%d", gui_win_x, gui_win_y);
469         int pos[2] = { gui_win_x, gui_win_y };
470         NSData *data = [NSData dataWithBytes:pos length:2*sizeof(int)];
471         [self queueMessage:SetWindowPositionMsgID data:data];
472     }
474     [self queueMessage:OpenWindowMsgID data:nil];
476     return YES;
479 - (void)clearAll
481     int type = ClearAllDrawType;
483     // Any draw commands in queue are effectively obsolete since this clearAll
484     // will negate any effect they have, therefore we may as well clear the
485     // draw queue.
486     [self clearDrawData];
488     [drawData appendBytes:&type length:sizeof(int)];
491 - (void)clearBlockFromRow:(int)row1 column:(int)col1
492                     toRow:(int)row2 column:(int)col2
494     int type = ClearBlockDrawType;
496     [drawData appendBytes:&type length:sizeof(int)];
498     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
499     [drawData appendBytes:&row1 length:sizeof(int)];
500     [drawData appendBytes:&col1 length:sizeof(int)];
501     [drawData appendBytes:&row2 length:sizeof(int)];
502     [drawData appendBytes:&col2 length:sizeof(int)];
505 - (void)deleteLinesFromRow:(int)row count:(int)count
506               scrollBottom:(int)bottom left:(int)left right:(int)right
508     int type = DeleteLinesDrawType;
510     [drawData appendBytes:&type length:sizeof(int)];
512     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
513     [drawData appendBytes:&row length:sizeof(int)];
514     [drawData appendBytes:&count length:sizeof(int)];
515     [drawData appendBytes:&bottom length:sizeof(int)];
516     [drawData appendBytes:&left length:sizeof(int)];
517     [drawData appendBytes:&right length:sizeof(int)];
519     if (left == 0 && right == gui.num_cols-1)
520         [self didChangeWholeLine];
523 - (void)drawString:(char_u*)s length:(int)len row:(int)row
524             column:(int)col cells:(int)cells flags:(int)flags
526     if (len <= 0) return;
528     int type = DrawStringDrawType;
530     [drawData appendBytes:&type length:sizeof(int)];
532     [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
533     [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
534     [drawData appendBytes:&specialColor length:sizeof(unsigned)];
535     [drawData appendBytes:&row length:sizeof(int)];
536     [drawData appendBytes:&col length:sizeof(int)];
537     [drawData appendBytes:&cells length:sizeof(int)];
538     [drawData appendBytes:&flags length:sizeof(int)];
539     [drawData appendBytes:&len length:sizeof(int)];
540     [drawData appendBytes:s length:len];
543 - (void)insertLinesFromRow:(int)row count:(int)count
544               scrollBottom:(int)bottom left:(int)left right:(int)right
546     int type = InsertLinesDrawType;
548     [drawData appendBytes:&type length:sizeof(int)];
550     [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
551     [drawData appendBytes:&row length:sizeof(int)];
552     [drawData appendBytes:&count length:sizeof(int)];
553     [drawData appendBytes:&bottom length:sizeof(int)];
554     [drawData appendBytes:&left length:sizeof(int)];
555     [drawData appendBytes:&right length:sizeof(int)];
557     if (left == 0 && right == gui.num_cols-1)
558         [self didChangeWholeLine];
561 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
562                fraction:(int)percent color:(int)color
564     int type = DrawCursorDrawType;
565     unsigned uc = MM_COLOR(color);
567     [drawData appendBytes:&type length:sizeof(int)];
569     [drawData appendBytes:&uc length:sizeof(unsigned)];
570     [drawData appendBytes:&row length:sizeof(int)];
571     [drawData appendBytes:&col length:sizeof(int)];
572     [drawData appendBytes:&shape length:sizeof(int)];
573     [drawData appendBytes:&percent length:sizeof(int)];
576 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
577                    numColumns:(int)nc invert:(int)invert
579     int type = DrawInvertedRectDrawType;
580     [drawData appendBytes:&type length:sizeof(int)];
582     [drawData appendBytes:&row length:sizeof(int)];
583     [drawData appendBytes:&col length:sizeof(int)];
584     [drawData appendBytes:&nr length:sizeof(int)];
585     [drawData appendBytes:&nc length:sizeof(int)];
586     [drawData appendBytes:&invert length:sizeof(int)];
589 - (void)update
591     // Keep running the run-loop until there is no more input to process.
592     while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
593             == kCFRunLoopRunHandledSource)
594         ;   // do nothing
597 - (void)flushQueue:(BOOL)force
599     // NOTE: This variable allows for better control over when the queue is
600     // flushed.  It can be set to YES at the beginning of a sequence of calls
601     // that may potentially add items to the queue, and then restored back to
602     // NO.
603     if (flushDisabled) return;
605     if ([drawData length] > 0) {
606         // HACK!  Detect changes to 'guifontwide'.
607         if (gui.wide_font != oldWideFont) {
608             gui_mch_free_font(oldWideFont);
609             oldWideFont = gui_mch_retain_font(gui.wide_font);
610             [self setFont:oldWideFont wide:YES];
611         }
613         int type = SetCursorPosDrawType;
614         [drawData appendBytes:&type length:sizeof(type)];
615         [drawData appendBytes:&gui.row length:sizeof(gui.row)];
616         [drawData appendBytes:&gui.col length:sizeof(gui.col)];
618         [self queueMessage:BatchDrawMsgID data:[drawData copy]];
619         [self clearDrawData];
620     }
622     if ([outputQueue count] > 0) {
623         [self insertVimStateMessage];
625         @try {
626             ASLogDebug(@"Flushing queue: %@",
627                        debugStringForMessageQueue(outputQueue));
628             [appProxy processInput:outputQueue forIdentifier:identifier];
629         }
630         @catch (NSException *ex) {
631             ASLogDebug(@"processInput:forIdentifer failed: reason=%@", ex);
632             if (![connection isValid]) {
633                 ASLogDebug(@"Connection is invalid, exit now!");
634                 ASLogDebug(@"waitForAck=%d got_int=%d", waitForAck, got_int);
635                 mch_exit(-1);
636             }
637         }
639         [outputQueue removeAllObjects];
640     }
643 - (BOOL)waitForInput:(int)milliseconds
645     // Return NO if we timed out waiting for input, otherwise return YES.
646     BOOL inputReceived = NO;
648     // Only start the run loop if the input queue is empty, otherwise process
649     // the input first so that the input on queue isn't delayed.
650     if ([inputQueue count]) {
651         inputReceived = YES;
652     } else {
653         // Wait for the specified amount of time, unless 'milliseconds' is
654         // negative in which case we wait "forever" (1e6 seconds translates to
655         // approximately 11 days).
656         CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
658         while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
659                 == kCFRunLoopRunHandledSource) {
660             // In order to ensure that all input on the run-loop has been
661             // processed we set the timeout to 0 and keep processing until the
662             // run-loop times out.
663             dt = 0.0;
664             inputReceived = YES;
665         }
666     }
668     // The above calls may have placed messages on the input queue so process
669     // it now.  This call may enter a blocking loop.
670     if ([inputQueue count] > 0)
671         [self processInputQueue];
673     return inputReceived;
676 - (void)exit
678     // NOTE: This is called if mch_exit() is called.  Since we assume here that
679     // the process has started properly, be sure to use exit() instead of
680     // mch_exit() to prematurely terminate a process (or set 'isTerminating'
681     // first).
683     // Make sure no connectionDidDie: notification is received now that we are
684     // already exiting.
685     [[NSNotificationCenter defaultCenter] removeObserver:self];
687     // The 'isTerminating' flag indicates that the frontend is also exiting so
688     // there is no need to flush any more output since the frontend won't look
689     // at it anyway.
690     if (!isTerminating && [connection isValid]) {
691         @try {
692             // Flush the entire queue in case a VimLeave autocommand added
693             // something to the queue.
694             [self queueMessage:CloseWindowMsgID data:nil];
695             ASLogDebug(@"Flush output queue before exit: %@",
696                        debugStringForMessageQueue(outputQueue));
697             [appProxy processInput:outputQueue forIdentifier:identifier];
698         }
699         @catch (NSException *ex) {
700             ASLogDebug(@"CloseWindowMsgID send failed: reason=%@", ex);
701         }
703         // NOTE: If Cmd-w was pressed to close the window the menu is briefly
704         // highlighted and during this pause the frontend won't receive any DO
705         // messages.  If the Vim process exits before this highlighting has
706         // finished Cocoa will emit the following error message:
707         //   *** -[NSMachPort handlePortMessage:]: dropping incoming DO message
708         //   because the connection or ports are invalid
709         // To avoid this warning we delay here.  If the warning still appears
710         // this delay may need to be increased.
711         usleep(150000);
712     }
714 #ifdef MAC_CLIENTSERVER
715     // The default connection is used for the client/server code.
716     if (vimServerConnection) {
717         [vimServerConnection setRootObject:nil];
718         [vimServerConnection invalidate];
719     }
720 #endif
723 - (void)selectTab:(int)index
725     index -= 1;
726     NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
727     [self queueMessage:SelectTabMsgID data:data];
730 - (void)updateTabBar
732     NSMutableData *data = [NSMutableData data];
734     int idx = tabpage_index(curtab) - 1;
735     [data appendBytes:&idx length:sizeof(int)];
737     tabpage_T *tp;
738     for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
739         // Count the number of windows in the tabpage.
740         //win_T *wp = tp->tp_firstwin;
741         //int wincount;
742         //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
743         //[data appendBytes:&wincount length:sizeof(int)];
745         int tabProp = MMTabInfoCount;
746         [data appendBytes:&tabProp length:sizeof(int)];
747         for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
748             // This function puts the label of the tab in the global 'NameBuff'.
749             get_tabline_label(tp, (tabProp == MMTabToolTip));
750             NSString *s = [NSString stringWithVimString:NameBuff];
751             int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
752             if (len < 0)
753                 len = 0;
755             [data appendBytes:&len length:sizeof(int)];
756             if (len > 0)
757                 [data appendBytes:[s UTF8String] length:len];
758         }
759     }
761     [self queueMessage:UpdateTabBarMsgID data:data];
764 - (BOOL)tabBarVisible
766     return tabBarVisible;
769 - (void)showTabBar:(BOOL)enable
771     tabBarVisible = enable;
773     int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
774     [self queueMessage:msgid data:nil];
777 - (void)setRows:(int)rows columns:(int)cols
779     int dim[] = { rows, cols };
780     NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
782     [self queueMessage:SetTextDimensionsMsgID data:data];
785 - (void)setWindowTitle:(char *)title
787     NSMutableData *data = [NSMutableData data];
788     int len = strlen(title);
789     if (len <= 0) return;
791     [data appendBytes:&len length:sizeof(int)];
792     [data appendBytes:title length:len];
794     [self queueMessage:SetWindowTitleMsgID data:data];
797 - (void)setDocumentFilename:(char *)filename
799     NSMutableData *data = [NSMutableData data];
800     int len = filename ? strlen(filename) : 0;
802     [data appendBytes:&len length:sizeof(int)];
803     if (len > 0)
804         [data appendBytes:filename length:len];
806     [self queueMessage:SetDocumentFilenameMsgID data:data];
809 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
811     char_u *s = NULL;
813     [self queueMessage:BrowseForFileMsgID properties:attr];
814     [self flushQueue:YES];
816     @try {
817         [self waitForDialogReturn];
819         if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
820             s = [dialogReturn vimStringSave];
822         [dialogReturn release];  dialogReturn = nil;
823     }
824     @catch (NSException *ex) {
825         ASLogDebug(@"Exception: reason=%@", ex);
826     }
828     return (char *)s;
831 - (oneway void)setDialogReturn:(in bycopy id)obj
833     ASLogDebug(@"%@", obj);
835     // NOTE: This is called by
836     //   - [MMVimController panelDidEnd:::], and
837     //   - [MMVimController alertDidEnd:::],
838     // to indicate that a save/open panel or alert has finished.
840     // We want to distinguish between "no dialog return yet" and "dialog
841     // returned nothing".  The former can be tested with dialogReturn == nil,
842     // the latter with dialogReturn == [NSNull null].
843     if (!obj) obj = [NSNull null];
845     if (obj != dialogReturn) {
846         [dialogReturn release];
847         dialogReturn = [obj retain];
848     }
851 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
853     int retval = 0;
855     [self queueMessage:ShowDialogMsgID properties:attr];
856     [self flushQueue:YES];
858     @try {
859         [self waitForDialogReturn];
861         if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
862                 && [dialogReturn count]) {
863             retval = [[dialogReturn objectAtIndex:0] intValue];
864             if (txtfield && [dialogReturn count] > 1) {
865                 NSString *retString = [dialogReturn objectAtIndex:1];
866                 char_u *ret = (char_u*)[retString UTF8String];
867 #ifdef FEAT_MBYTE
868                 ret = CONVERT_FROM_UTF8(ret);
869 #endif
870                 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
871 #ifdef FEAT_MBYTE
872                 CONVERT_FROM_UTF8_FREE(ret);
873 #endif
874             }
875         }
877         [dialogReturn release]; dialogReturn = nil;
878     }
879     @catch (NSException *ex) {
880         ASLogDebug(@"Exception: reason=%@", ex);
881     }
883     return retval;
886 - (void)showToolbar:(int)enable flags:(int)flags
888     NSMutableData *data = [NSMutableData data];
890     [data appendBytes:&enable length:sizeof(int)];
891     [data appendBytes:&flags length:sizeof(int)];
893     [self queueMessage:ShowToolbarMsgID data:data];
896 - (void)createScrollbarWithIdentifier:(int32_t)ident type:(int)type
898     NSMutableData *data = [NSMutableData data];
900     [data appendBytes:&ident length:sizeof(int32_t)];
901     [data appendBytes:&type length:sizeof(int)];
903     [self queueMessage:CreateScrollbarMsgID data:data];
906 - (void)destroyScrollbarWithIdentifier:(int32_t)ident
908     NSMutableData *data = [NSMutableData data];
909     [data appendBytes:&ident length:sizeof(int32_t)];
911     [self queueMessage:DestroyScrollbarMsgID data:data];
914 - (void)showScrollbarWithIdentifier:(int32_t)ident state:(int)visible
916     NSMutableData *data = [NSMutableData data];
918     [data appendBytes:&ident length:sizeof(int32_t)];
919     [data appendBytes:&visible length:sizeof(int)];
921     [self queueMessage:ShowScrollbarMsgID data:data];
924 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(int32_t)ident
926     NSMutableData *data = [NSMutableData data];
928     [data appendBytes:&ident length:sizeof(int32_t)];
929     [data appendBytes:&pos length:sizeof(int)];
930     [data appendBytes:&len length:sizeof(int)];
932     [self queueMessage:SetScrollbarPositionMsgID data:data];
935 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
936                     identifier:(int32_t)ident
938     float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
939     float prop = (float)size/(max+1);
940     if (fval < 0) fval = 0;
941     else if (fval > 1.0f) fval = 1.0f;
942     if (prop < 0) prop = 0;
943     else if (prop > 1.0f) prop = 1.0f;
945     NSMutableData *data = [NSMutableData data];
947     [data appendBytes:&ident length:sizeof(int32_t)];
948     [data appendBytes:&fval length:sizeof(float)];
949     [data appendBytes:&prop length:sizeof(float)];
951     [self queueMessage:SetScrollbarThumbMsgID data:data];
954 - (void)setFont:(GuiFont)font wide:(BOOL)wide
956     NSString *fontName = (NSString *)font;
957     float size = 0;
958     NSArray *components = [fontName componentsSeparatedByString:@":"];
959     if ([components count] == 2) {
960         size = [[components lastObject] floatValue];
961         fontName = [components objectAtIndex:0];
962     }
964     int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
965     NSMutableData *data = [NSMutableData data];
966     [data appendBytes:&size length:sizeof(float)];
967     [data appendBytes:&len length:sizeof(int)];
969     if (len > 0)
970         [data appendBytes:[fontName UTF8String] length:len];
971     else if (!wide)
972         return;     // Only the wide font can be set to nothing
974     [self queueMessage:(wide ? SetWideFontMsgID : SetFontMsgID) data:data];
977 - (void)executeActionWithName:(NSString *)name
979     int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
981     if (len > 0) {
982         NSMutableData *data = [NSMutableData data];
984         [data appendBytes:&len length:sizeof(int)];
985         [data appendBytes:[name UTF8String] length:len];
987         [self queueMessage:ExecuteActionMsgID data:data];
988     }
991 - (void)setMouseShape:(int)shape
993     NSMutableData *data = [NSMutableData data];
994     [data appendBytes:&shape length:sizeof(int)];
995     [self queueMessage:SetMouseShapeMsgID data:data];
998 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
1000     // Vim specifies times in milliseconds, whereas Cocoa wants them in
1001     // seconds.
1002     blinkWaitInterval = .001f*wait;
1003     blinkOnInterval = .001f*on;
1004     blinkOffInterval = .001f*off;
1007 - (void)startBlink
1009     if (blinkTimer) {
1010         [blinkTimer invalidate];
1011         [blinkTimer release];
1012         blinkTimer = nil;
1013     }
1015     if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
1016             && gui.in_focus) {
1017         blinkState = MMBlinkStateOn;
1018         blinkTimer =
1019             [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
1020                                               target:self
1021                                             selector:@selector(blinkTimerFired:)
1022                                             userInfo:nil repeats:NO] retain];
1023         gui_update_cursor(TRUE, FALSE);
1024         [self flushQueue:YES];
1025     }
1028 - (void)stopBlink
1030     if (MMBlinkStateOff == blinkState) {
1031         gui_update_cursor(TRUE, FALSE);
1032         [self flushQueue:YES];
1033     }
1035     blinkState = MMBlinkStateNone;
1038 - (void)adjustLinespace:(int)linespace
1040     NSMutableData *data = [NSMutableData data];
1041     [data appendBytes:&linespace length:sizeof(int)];
1042     [self queueMessage:AdjustLinespaceMsgID data:data];
1045 - (void)activate
1047     [self queueMessage:ActivateMsgID data:nil];
1050 - (void)setPreEditRow:(int)row column:(int)col
1052     NSMutableData *data = [NSMutableData data];
1053     [data appendBytes:&row length:sizeof(int)];
1054     [data appendBytes:&col length:sizeof(int)];
1055     [self queueMessage:SetPreEditPositionMsgID data:data];
1058 - (int)lookupColorWithKey:(NSString *)key
1060     if (!(key && [key length] > 0))
1061         return INVALCOLOR;
1063     NSString *stripKey = [[[[key lowercaseString]
1064         stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
1065             componentsSeparatedByString:@" "]
1066                componentsJoinedByString:@""];
1068     if (stripKey && [stripKey length] > 0) {
1069         // First of all try to lookup key in the color dictionary; note that
1070         // all keys in this dictionary are lowercase with no whitespace.
1071         id obj = [colorDict objectForKey:stripKey];
1072         if (obj) return [obj intValue];
1074         // The key was not in the dictionary; is it perhaps of the form
1075         // #rrggbb?
1076         if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
1077             NSScanner *scanner = [NSScanner scannerWithString:stripKey];
1078             [scanner setScanLocation:1];
1079             unsigned hex = 0;
1080             if ([scanner scanHexInt:&hex]) {
1081                 return (int)hex;
1082             }
1083         }
1085         // As a last resort, check if it is one of the system defined colors.
1086         // The keys in this dictionary are also lowercase with no whitespace.
1087         obj = [sysColorDict objectForKey:stripKey];
1088         if (obj) {
1089             NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
1090             if (col) {
1091                 CGFloat r, g, b, a;
1092                 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
1093                 [col getRed:&r green:&g blue:&b alpha:&a];
1094                 return (((int)(r*255+.5f) & 0xff) << 16)
1095                      + (((int)(g*255+.5f) & 0xff) << 8)
1096                      +  ((int)(b*255+.5f) & 0xff);
1097             }
1098         }
1099     }
1101     ASLogNotice(@"No color with key %@ found.", stripKey);
1102     return INVALCOLOR;
1105 - (BOOL)hasSpecialKeyWithValue:(char_u *)value
1107     int i;
1108     for (i = 0; special_keys[i].key_sym != 0; i++) {
1109         if (value[0] == special_keys[i].vim_code0
1110                 && value[1] == special_keys[i].vim_code1)
1111             return YES;
1112     }
1114     return NO;
1117 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1119     NSMutableData *data = [NSMutableData data];
1120     [data appendBytes:&fuoptions length:sizeof(int)];
1121     bg = MM_COLOR(bg);
1122     [data appendBytes:&bg length:sizeof(int)];
1123     [self queueMessage:EnterFullscreenMsgID data:data];
1126 - (void)leaveFullscreen
1128     [self queueMessage:LeaveFullscreenMsgID data:nil];
1131 - (void)setFullscreenBackgroundColor:(int)color
1133     NSMutableData *data = [NSMutableData data];
1134     color = MM_COLOR(color);
1135     [data appendBytes:&color length:sizeof(int)];
1137     [self queueMessage:SetFullscreenColorMsgID data:data];
1140 - (void)setAntialias:(BOOL)antialias
1142     int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1144     [self queueMessage:msgid data:nil];
1147 - (void)updateModifiedFlag
1149     // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1150     // vice versa.
1151     int msgid = [self checkForModifiedBuffers]
1152             ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1154     [self queueMessage:msgid data:nil];
1157 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1159     // Look for Ctrl-C immediately instead of waiting until the input queue is
1160     // processed since that only happens in waitForInput: (and Vim regularly
1161     // checks for Ctrl-C in between waiting for input).  Note that the flag
1162     // ctrl_c_interrupts is 0 e.g. when the user has mappings to something like
1163     // <C-c>g.  Also it seems the flag intr_char is 0 when MacVim was started
1164     // from Finder whereas it is 0x03 (= Ctrl_C) when started from Terminal.
1165     //
1166     // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1167     // which waits on the run loop will fail to detect this message (e.g. in
1168     // waitForConnectionAcknowledgement).
1170     if (KeyDownMsgID == msgid && data != nil && ctrl_c_interrupts) {
1171         const void *bytes = [data bytes];
1172         /*unsigned mods = *((unsigned*)bytes);*/  bytes += sizeof(unsigned);
1173         /*unsigned code = *((unsigned*)bytes);*/  bytes += sizeof(unsigned);
1174         unsigned len  = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1175         if (1 == len) {
1176             char_u *str = (char_u*)bytes;
1177             if (str[0] == Ctrl_C || (str[0] == intr_char && intr_char != 0)) {
1178                 ASLogDebug(@"Got INT, str[0]=%#x ctrl_c_interrupts=%d "
1179                         "intr_char=%#x", str[0], ctrl_c_interrupts, intr_char);
1180                 got_int = TRUE;
1181                 [inputQueue removeAllObjects];
1182                 return;
1183             }
1184         }
1185     } else if (TerminateNowMsgID == msgid) {
1186         // Terminate immediately (the frontend is about to quit or this process
1187         // was aborted).  Don't preserve modified files since the user would
1188         // already have been presented with a dialog warning if there were any
1189         // modified files when we get here.
1190         isTerminating = YES;
1191         getout(0);
1192         return;
1193     }
1195     // Remove all previous instances of this message from the input queue, else
1196     // the input queue may fill up as a result of Vim not being able to keep up
1197     // with the speed at which new messages are received.
1198     // Keyboard input is never dropped, unless the input represents an
1199     // auto-repeated key.
1201     BOOL isKeyRepeat = NO;
1202     BOOL isKeyboardInput = NO;
1204     if (data && KeyDownMsgID == msgid) {
1205         isKeyboardInput = YES;
1207         // The lowest bit of the first int is set if this key is a repeat.
1208         int flags = *((int*)[data bytes]);
1209         if (flags & 1)
1210             isKeyRepeat = YES;
1211     }
1213     // Keyboard input is not removed from the queue; repeats are ignored if
1214     // there already is keyboard input on the input queue.
1215     if (isKeyRepeat || !isKeyboardInput) {
1216         int i, count = [inputQueue count];
1217         for (i = 1; i < count; i+=2) {
1218             if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1219                 if (isKeyRepeat)
1220                     return;
1222                 [inputQueue removeObjectAtIndex:i];
1223                 [inputQueue removeObjectAtIndex:i-1];
1224                 break;
1225             }
1226         }
1227     }
1229     [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1230     [inputQueue addObject:(data ? (id)data : [NSNull null])];
1233 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1234                   errorString:(out bycopy NSString **)errstr
1236     return evalExprCocoa(expr, errstr);
1240 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1242     NSString *eval = nil;
1243     char_u *s = (char_u*)[expr UTF8String];
1245 #ifdef FEAT_MBYTE
1246     s = CONVERT_FROM_UTF8(s);
1247 #endif
1249     char_u *res = eval_client_expr_to_string(s);
1251 #ifdef FEAT_MBYTE
1252     CONVERT_FROM_UTF8_FREE(s);
1253 #endif
1255     if (res != NULL) {
1256         s = res;
1257 #ifdef FEAT_MBYTE
1258         s = CONVERT_TO_UTF8(s);
1259 #endif
1260         eval = [NSString stringWithUTF8String:(char*)s];
1261 #ifdef FEAT_MBYTE
1262         CONVERT_TO_UTF8_FREE(s);
1263 #endif
1264         vim_free(res);
1265     }
1267     return eval;
1270 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1272     // TODO: This method should share code with clip_mch_request_selection().
1274     if (VIsual_active && (State & NORMAL) && clip_star.available) {
1275         // If there is no pasteboard, return YES to indicate that there is text
1276         // to copy.
1277         if (!pboard)
1278             return YES;
1280         clip_copy_selection();
1282         // Get the text to put on the pasteboard.
1283         long_u llen = 0; char_u *str = 0;
1284         int type = clip_convert_selection(&str, &llen, &clip_star);
1285         if (type < 0)
1286             return NO;
1287         
1288         // TODO: Avoid overflow.
1289         int len = (int)llen;
1290 #ifdef FEAT_MBYTE
1291         if (output_conv.vc_type != CONV_NONE) {
1292             char_u *conv_str = string_convert(&output_conv, str, &len);
1293             if (conv_str) {
1294                 vim_free(str);
1295                 str = conv_str;
1296             }
1297         }
1298 #endif
1300         NSString *string = [[NSString alloc]
1301             initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1303         NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1304         [pboard declareTypes:types owner:nil];
1305         BOOL ok = [pboard setString:string forType:NSStringPboardType];
1306     
1307         [string release];
1308         vim_free(str);
1310         return ok;
1311     }
1313     return NO;
1316 - (oneway void)addReply:(in bycopy NSString *)reply
1317                  server:(in byref id <MMVimServerProtocol>)server
1319     ASLogDebug(@"reply=%@ server=%@", reply, (id)server);
1321     // Replies might come at any time and in any order so we keep them in an
1322     // array inside a dictionary with the send port used as key.
1324     NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1325     // HACK! Assume connection uses mach ports.
1326     int port = [(NSMachPort*)[conn sendPort] machPort];
1327     NSNumber *key = [NSNumber numberWithInt:port];
1329     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1330     if (!replies) {
1331         replies = [NSMutableArray array];
1332         [serverReplyDict setObject:replies forKey:key];
1333     }
1335     [replies addObject:reply];
1338 - (void)addInput:(in bycopy NSString *)input
1339           client:(in byref id <MMVimClientProtocol>)client
1341     ASLogDebug(@"input=%@ client=%@", input, (id)client);
1343     // NOTE: We don't call addInput: here because it differs from
1344     // server_to_input_buf() in that it always sets the 'silent' flag and we
1345     // don't want the MacVim client/server code to behave differently from
1346     // other platforms.
1347     char_u *s = [input vimStringSave];
1348     server_to_input_buf(s);
1349     vim_free(s);
1351     [self addClient:(id)client];
1354 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1355                  client:(in byref id <MMVimClientProtocol>)client
1357     [self addClient:(id)client];
1358     return [self evaluateExpression:expr];
1361 - (void)registerServerWithName:(NSString *)name
1363     NSString *svrName = name;
1364     unsigned i;
1366     if (vimServerConnection) // Paranoia check, should always be nil
1367         [vimServerConnection release];
1369     vimServerConnection = [[NSConnection alloc]
1370                                             initWithReceivePort:[NSPort port]
1371                                                        sendPort:nil];
1373     for (i = 0; i < MMServerMax; ++i) {
1374         NSString *connName = [self connectionNameFromServerName:svrName];
1376         if ([vimServerConnection registerName:connName]) {
1377             ASLogInfo(@"Registered server with name: %@", svrName);
1379             // TODO: Set request/reply time-outs to something else?
1380             //
1381             // Don't wait for requests (time-out means that the message is
1382             // dropped).
1383             [vimServerConnection setRequestTimeout:0];
1384             //[vimServerConnection setReplyTimeout:MMReplyTimeout];
1385             [vimServerConnection setRootObject:self];
1387             // NOTE: 'serverName' is a global variable
1388             serverName = [svrName vimStringSave];
1389 #ifdef FEAT_EVAL
1390             set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1391 #endif
1392 #ifdef FEAT_TITLE
1393             need_maketitle = TRUE;
1394 #endif
1395             [self queueMessage:SetServerNameMsgID
1396                         data:[svrName dataUsingEncoding:NSUTF8StringEncoding]];
1397             break;
1398         }
1400         svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1401     }
1404 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1405                reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1406               silent:(BOOL)silent
1408     // NOTE: If 'name' equals 'serverName' then the request is local (client
1409     // and server are the same).  This case is not handled separately, so a
1410     // connection will be set up anyway (this simplifies the code).
1412     NSConnection *conn = [self connectionForServerName:name];
1413     if (!conn) {
1414         if (!silent) {
1415             char_u *s = (char_u*)[name UTF8String];
1416 #ifdef FEAT_MBYTE
1417             s = CONVERT_FROM_UTF8(s);
1418 #endif
1419             EMSG2(_(e_noserver), s);
1420 #ifdef FEAT_MBYTE
1421             CONVERT_FROM_UTF8_FREE(s);
1422 #endif
1423         }
1424         return NO;
1425     }
1427     if (port) {
1428         // HACK! Assume connection uses mach ports.
1429         *port = [(NSMachPort*)[conn sendPort] machPort];
1430     }
1432     id proxy = [conn rootProxy];
1433     [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1435     @try {
1436         if (expr) {
1437             NSString *eval = [proxy evaluateExpression:string client:self];
1438             if (reply) {
1439                 if (eval) {
1440                     *reply = [eval vimStringSave];
1441                 } else {
1442                     *reply = vim_strsave((char_u*)_(e_invexprmsg));
1443                 }
1444             }
1446             if (!eval)
1447                 return NO;
1448         } else {
1449             [proxy addInput:string client:self];
1450         }
1451     }
1452     @catch (NSException *ex) {
1453         ASLogDebug(@"Exception: reason=%@", ex);
1454         return NO;
1455     }
1457     return YES;
1460 - (NSArray *)serverList
1462     NSArray *list = nil;
1464     if ([self connection]) {
1465         id proxy = [connection rootProxy];
1466         [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1468         @try {
1469             list = [proxy serverList];
1470         }
1471         @catch (NSException *ex) {
1472             ASLogDebug(@"serverList failed: reason=%@", ex);
1473         }
1474     } else {
1475         // We get here if a --remote flag is used before MacVim has started.
1476         ASLogInfo(@"No connection to MacVim, server listing not possible.");
1477     }
1479     return list;
1482 - (NSString *)peekForReplyOnPort:(int)port
1484     ASLogDebug(@"port=%d", port);
1486     NSNumber *key = [NSNumber numberWithInt:port];
1487     NSMutableArray *replies = [serverReplyDict objectForKey:key];
1488     if (replies && [replies count]) {
1489         ASLogDebug(@"    %d replies, topmost is: %@", [replies count],
1490                    [replies objectAtIndex:0]);
1491         return [replies objectAtIndex:0];
1492     }
1494     ASLogDebug(@"    No replies");
1495     return nil;
1498 - (NSString *)waitForReplyOnPort:(int)port
1500     ASLogDebug(@"port=%d", port);
1501     
1502     NSConnection *conn = [self connectionForServerPort:port];
1503     if (!conn)
1504         return nil;
1506     NSNumber *key = [NSNumber numberWithInt:port];
1507     NSMutableArray *replies = nil;
1508     NSString *reply = nil;
1510     // Wait for reply as long as the connection to the server is valid (unless
1511     // user interrupts wait with Ctrl-C).
1512     while (!got_int && [conn isValid] &&
1513             !(replies = [serverReplyDict objectForKey:key])) {
1514         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1515                                  beforeDate:[NSDate distantFuture]];
1516     }
1518     if (replies) {
1519         if ([replies count] > 0) {
1520             reply = [[replies objectAtIndex:0] retain];
1521             ASLogDebug(@"    Got reply: %@", reply);
1522             [replies removeObjectAtIndex:0];
1523             [reply autorelease];
1524         }
1526         if ([replies count] == 0)
1527             [serverReplyDict removeObjectForKey:key];
1528     }
1530     return reply;
1533 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1535     id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1536     if (client) {
1537         @try {
1538             ASLogDebug(@"reply=%@ port=%d", reply, port);
1539             [client addReply:reply server:self];
1540             return YES;
1541         }
1542         @catch (NSException *ex) {
1543             ASLogDebug(@"addReply:server: failed: reason=%@", ex);
1544         }
1545     } else {
1546         ASLogNotice(@"server2client failed; no client with id %d", port);
1547     }
1549     return NO;
1552 - (BOOL)waitForAck
1554     return waitForAck;
1557 - (void)setWaitForAck:(BOOL)yn
1559     waitForAck = yn;
1562 - (void)waitForConnectionAcknowledgement
1564     if (!waitForAck) return;
1566     while (waitForAck && !got_int && [connection isValid]) {
1567         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1568                                  beforeDate:[NSDate distantFuture]];
1569         ASLogDebug(@"  waitForAck=%d got_int=%d isValid=%d",
1570                    waitForAck, got_int, [connection isValid]);
1571     }
1573     if (waitForAck) {
1574         ASLogDebug(@"Never received a connection acknowledgement");
1575         [[NSNotificationCenter defaultCenter] removeObserver:self];
1576         [appProxy release];  appProxy = nil;
1578         // NOTE: We intentionally do not call mch_exit() since this in turn
1579         // will lead to -[MMBackend exit] getting called which we want to
1580         // avoid.
1581         exit(0);
1582     }
1584     ASLogInfo(@"Connection acknowledgement received");
1585     [self processInputQueue];
1588 - (oneway void)acknowledgeConnection
1590     ASLogDebug(@"");
1591     waitForAck = NO;
1594 - (BOOL)imState
1596     return imState;
1599 - (void)setImState:(BOOL)activated
1601     imState = activated;
1604 static void netbeansReadCallback(CFSocketRef s,
1605                                  CFSocketCallBackType callbackType,
1606                                  CFDataRef address,
1607                                  const void *data,
1608                                  void *info)
1610     // NetBeans socket is readable.
1611     [[MMBackend sharedInstance] messageFromNetbeans];
1614 - (void)messageFromNetbeans
1616     [inputQueue addObject:[NSNumber numberWithInt:NetBeansMsgID]];
1617     [inputQueue addObject:[NSNull null]];
1620 - (void)setNetbeansSocket:(int)socket
1622     if (netbeansSocket) {
1623         CFRelease(netbeansSocket);
1624         netbeansSocket = NULL;
1625     }
1627     if (netbeansRunLoopSource) {
1628         CFRunLoopSourceInvalidate(netbeansRunLoopSource);
1629         netbeansRunLoopSource = NULL;
1630     }
1632     if (socket == -1)
1633         return;
1635     // Tell CFRunLoop that we are interested in NetBeans socket input.
1636     netbeansSocket = CFSocketCreateWithNative(kCFAllocatorDefault,
1637                                               socket,
1638                                               kCFSocketReadCallBack,
1639                                               &netbeansReadCallback,
1640                                               NULL);
1641     netbeansRunLoopSource = CFSocketCreateRunLoopSource(NULL,
1642                                                         netbeansSocket,
1643                                                         0);
1644     CFRunLoopAddSource(CFRunLoopGetCurrent(),
1645                        netbeansRunLoopSource,
1646                        kCFRunLoopCommonModes);
1649 @end // MMBackend
1653 @implementation MMBackend (Private)
1655 - (void)clearDrawData
1657     [drawData setLength:0];
1658     numWholeLineChanges = offsetForDrawDataPrune = 0;
1661 - (void)didChangeWholeLine
1663     // It may happen that draw queue is filled up with lots of changes that
1664     // affect a whole row.  If the number of such changes equals twice the
1665     // number of visible rows then we can prune some commands off the queue.
1666     //
1667     // NOTE: If we don't perform this pruning the draw queue may grow
1668     // indefinitely if Vim were to repeatedly send draw commands without ever
1669     // waiting for new input (that's when the draw queue is flushed).  The one
1670     // instance I know where this can happen is when a command is executed in
1671     // the shell (think ":grep" with thousands of matches).
1673     ++numWholeLineChanges;
1674     if (numWholeLineChanges == gui.num_rows) {
1675         // Remember the offset to prune up to.
1676         offsetForDrawDataPrune = [drawData length];
1677     } else if (numWholeLineChanges == 2*gui.num_rows) {
1678         // Delete all the unnecessary draw commands.
1679         NSMutableData *d = [[NSMutableData alloc]
1680                     initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1681                            length:[drawData length] - offsetForDrawDataPrune];
1682         offsetForDrawDataPrune = [d length];
1683         numWholeLineChanges -= gui.num_rows;
1684         [drawData release];
1685         drawData = d;
1686     }
1689 - (void)waitForDialogReturn
1691     // Keep processing the run loop until a dialog returns.  To avoid getting
1692     // stuck in an endless loop (could happen if the setDialogReturn: message
1693     // was lost) we also do some paranoia checks.
1694     //
1695     // Note that in Cocoa the user can still resize windows and select menu
1696     // items while a sheet is being displayed, so we can't just wait for the
1697     // first message to arrive and assume that is the setDialogReturn: call.
1699     while (nil == dialogReturn && !got_int && [connection isValid])
1700         [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1701                                  beforeDate:[NSDate distantFuture]];
1703     // Search for any resize messages on the input queue.  All other messages
1704     // on the input queue are dropped.  The reason why we single out resize
1705     // messages is because the user may have resized the window while a sheet
1706     // was open.
1707     int i, count = [inputQueue count];
1708     if (count > 0) {
1709         id textDimData = nil;
1710         if (count%2 == 0) {
1711             for (i = count-2; i >= 0; i -= 2) {
1712                 int msgid = [[inputQueue objectAtIndex:i] intValue];
1713                 if (SetTextDimensionsMsgID == msgid) {
1714                     textDimData = [[inputQueue objectAtIndex:i+1] retain];
1715                     break;
1716                 }
1717             }
1718         }
1720         [inputQueue removeAllObjects];
1722         if (textDimData) {
1723             [inputQueue addObject:
1724                     [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1725             [inputQueue addObject:textDimData];
1726             [textDimData release];
1727         }
1728     }
1731 - (void)insertVimStateMessage
1733     // NOTE: This is the place to add Vim state that needs to be accessed from
1734     // MacVim.  Do not add state that could potentially require lots of memory
1735     // since this message gets sent each time the output queue is forcibly
1736     // flushed (e.g. storing the currently selected text would be a bad idea).
1737     // We take this approach of "pushing" the state to MacVim to avoid having
1738     // to make synchronous calls from MacVim to Vim in order to get state.
1740     BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1741     int numTabs = tabpage_index(NULL) - 1;
1742     if (numTabs < 0)
1743         numTabs = 0;
1745     NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1746         [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1747         [NSNumber numberWithInt:p_mh], @"p_mh",
1748         [NSNumber numberWithBool:mmta], @"p_mmta",
1749         [NSNumber numberWithInt:numTabs], @"numTabs",
1750         nil];
1752     // Put the state before all other messages.
1753     int msgid = SetVimStateMsgID;
1754     [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1755     [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1756                       atIndex:0];
1759 - (void)processInputQueue
1761     if ([inputQueue count] == 0) return;
1763     // NOTE: One of the input events may cause this method to be called
1764     // recursively, so copy the input queue to a local variable and clear the
1765     // queue before starting to process input events (otherwise we could get
1766     // stuck in an endless loop).
1767     NSArray *q = [inputQueue copy];
1768     unsigned i, count = [q count];
1770     [inputQueue removeAllObjects];
1772     for (i = 1; i < count; i+=2) {
1773         int msgid = [[q objectAtIndex:i-1] intValue];
1774         id data = [q objectAtIndex:i];
1775         if ([data isEqual:[NSNull null]])
1776             data = nil;
1778         ASLogDebug(@"(%d) %s", i, MessageStrings[msgid]);
1779         [self handleInputEvent:msgid data:data];
1780     }
1782     [q release];
1786 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1788     if (KeyDownMsgID == msgid) {
1789         if (!data) return;
1790         const void *bytes = [data bytes];
1791         unsigned mods = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1792         unsigned code = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1793         unsigned len  = *((unsigned*)bytes);  bytes += sizeof(unsigned);
1794         NSString *key = [[NSString alloc] initWithBytes:bytes
1795                                                  length:len
1796                                                encoding:NSUTF8StringEncoding];
1797         mods = eventModifierFlagsToVimModMask(mods);
1799         [self doKeyDown:key keyCode:code modifiers:mods];
1800         [key release];
1801     } else if (ScrollWheelMsgID == msgid) {
1802         if (!data) return;
1803         const void *bytes = [data bytes];
1805         int row = *((int*)bytes);  bytes += sizeof(int);
1806         int col = *((int*)bytes);  bytes += sizeof(int);
1807         int flags = *((int*)bytes);  bytes += sizeof(int);
1808         float dy = *((float*)bytes);  bytes += sizeof(float);
1810         int button = MOUSE_5;
1811         if (dy > 0) button = MOUSE_4;
1813         flags = eventModifierFlagsToVimMouseModMask(flags);
1815         int numLines = (int)round(dy);
1816         if (numLines < 0) numLines = -numLines;
1817         if (numLines == 0) numLines = 1;
1819 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1820         gui.scroll_wheel_force = numLines;
1821 #endif
1823         gui_send_mouse_event(button, col, row, NO, flags);
1824     } else if (MouseDownMsgID == msgid) {
1825         if (!data) return;
1826         const void *bytes = [data bytes];
1828         int row = *((int*)bytes);  bytes += sizeof(int);
1829         int col = *((int*)bytes);  bytes += sizeof(int);
1830         int button = *((int*)bytes);  bytes += sizeof(int);
1831         int flags = *((int*)bytes);  bytes += sizeof(int);
1832         int count = *((int*)bytes);  bytes += sizeof(int);
1834         button = eventButtonNumberToVimMouseButton(button);
1835         if (button >= 0) {
1836             flags = eventModifierFlagsToVimMouseModMask(flags);
1837             gui_send_mouse_event(button, col, row, count>1, flags);
1838         }
1839     } else if (MouseUpMsgID == msgid) {
1840         if (!data) return;
1841         const void *bytes = [data bytes];
1843         int row = *((int*)bytes);  bytes += sizeof(int);
1844         int col = *((int*)bytes);  bytes += sizeof(int);
1845         int flags = *((int*)bytes);  bytes += sizeof(int);
1847         flags = eventModifierFlagsToVimMouseModMask(flags);
1849         gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1850     } else if (MouseDraggedMsgID == msgid) {
1851         if (!data) return;
1852         const void *bytes = [data bytes];
1854         int row = *((int*)bytes);  bytes += sizeof(int);
1855         int col = *((int*)bytes);  bytes += sizeof(int);
1856         int flags = *((int*)bytes);  bytes += sizeof(int);
1858         flags = eventModifierFlagsToVimMouseModMask(flags);
1860         gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1861     } else if (MouseMovedMsgID == msgid) {
1862         const void *bytes = [data bytes];
1863         int row = *((int*)bytes);  bytes += sizeof(int);
1864         int col = *((int*)bytes);  bytes += sizeof(int);
1866         gui_mouse_moved(col, row);
1867     } else if (AddInputMsgID == msgid) {
1868         NSString *string = [[NSString alloc] initWithData:data
1869                 encoding:NSUTF8StringEncoding];
1870         if (string) {
1871             [self addInput:string];
1872             [string release];
1873         }
1874     } else if (SelectTabMsgID == msgid) {
1875         if (!data) return;
1876         const void *bytes = [data bytes];
1877         int idx = *((int*)bytes) + 1;
1878         send_tabline_event(idx);
1879     } else if (CloseTabMsgID == msgid) {
1880         if (!data) return;
1881         const void *bytes = [data bytes];
1882         int idx = *((int*)bytes) + 1;
1883         send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1884     } else if (AddNewTabMsgID == msgid) {
1885         send_tabline_menu_event(0, TABLINE_MENU_NEW);
1886     } else if (DraggedTabMsgID == msgid) {
1887         if (!data) return;
1888         const void *bytes = [data bytes];
1889         // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1890         // based.
1891         int idx = *((int*)bytes);
1893         tabpage_move(idx);
1894     } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1895             || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1896         if (!data) return;
1897         const void *bytes = [data bytes];
1898         int rows = Rows;
1899         if (SetTextColumnsMsgID != msgid) {
1900             rows = *((int*)bytes);  bytes += sizeof(int);
1901         }
1902         int cols = Columns;
1903         if (SetTextRowsMsgID != msgid) {
1904             cols = *((int*)bytes);  bytes += sizeof(int);
1905         }
1907         NSData *d = data;
1908         if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1909             int dim[2] = { rows, cols };
1910             d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1911             msgid = SetTextDimensionsReplyMsgID;
1912         }
1914         if (SetTextDimensionsMsgID == msgid)
1915             msgid = SetTextDimensionsReplyMsgID;
1917         // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1918         // gui_resize_shell(), so we have to manually set the rows and columns
1919         // here since MacVim doesn't change the rows and columns to avoid
1920         // inconsistent states between Vim and MacVim.  The message sent back
1921         // indicates that it is a reply to a message that originated in MacVim
1922         // since we need to be able to determine where a message originated.
1923         [self queueMessage:msgid data:d];
1925         gui_resize_shell(cols, rows);
1926     } else if (ExecuteMenuMsgID == msgid) {
1927         NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1928         if (attrs) {
1929             NSArray *desc = [attrs objectForKey:@"descriptor"];
1930             vimmenu_T *menu = menu_for_descriptor(desc);
1931             if (menu)
1932                 gui_menu_cb(menu);
1933         }
1934     } else if (ToggleToolbarMsgID == msgid) {
1935         [self handleToggleToolbar];
1936     } else if (ScrollbarEventMsgID == msgid) {
1937         [self handleScrollbarEvent:data];
1938     } else if (SetFontMsgID == msgid) {
1939         [self handleSetFont:data];
1940     } else if (VimShouldCloseMsgID == msgid) {
1941         gui_shell_closed();
1942     } else if (DropFilesMsgID == msgid) {
1943         [self handleDropFiles:data];
1944     } else if (DropStringMsgID == msgid) {
1945         [self handleDropString:data];
1946     } else if (GotFocusMsgID == msgid) {
1947         if (!gui.in_focus)
1948             [self focusChange:YES];
1949     } else if (LostFocusMsgID == msgid) {
1950         if (gui.in_focus)
1951             [self focusChange:NO];
1952     } else if (SetMouseShapeMsgID == msgid) {
1953         const void *bytes = [data bytes];
1954         int shape = *((int*)bytes);  bytes += sizeof(int);
1955         update_mouseshape(shape);
1956     } else if (XcodeModMsgID == msgid) {
1957         [self handleXcodeMod:data];
1958     } else if (OpenWithArgumentsMsgID == msgid) {
1959         [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1960     } else if (FindReplaceMsgID == msgid) {
1961         [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1962     } else if (ActivatedImMsgID == msgid) {
1963         [self setImState:YES];
1964     } else if (DeactivatedImMsgID == msgid) {
1965         [self setImState:NO];
1966     } else if (NetBeansMsgID == msgid) {
1967 #ifdef FEAT_NETBEANS_INTG
1968         messageFromNetbeansMacVim();
1969 #endif
1970     } else if (SetMarkedTextMsgID == msgid) {
1971         [self handleMarkedText:data];
1972     } else if (ZoomMsgID == msgid) {
1973         if (!data) return;
1974         const void *bytes = [data bytes];
1975         int rows = *((int*)bytes);  bytes += sizeof(int);
1976         int cols = *((int*)bytes);  bytes += sizeof(int);
1977         //int zoom = *((int*)bytes);  bytes += sizeof(int);
1979         // NOTE: The frontend sends zoom messages here causing us to
1980         // immediately resize the shell and mirror the message back to the
1981         // frontend.  This is done to ensure that the draw commands reach the
1982         // frontend before the window actually changes size in order to avoid
1983         // flickering.  (Also see comment in SetTextDimensionsReplyMsgID
1984         // regarding resizing.)
1985         [self queueMessage:ZoomMsgID data:data];
1986         gui_resize_shell(cols, rows);
1987     } else if (SetWindowPositionMsgID == msgid) {
1988         if (!data) return;
1989         const void *bytes = [data bytes];
1990         winposX = *((int*)bytes);  bytes += sizeof(int);
1991         winposY = *((int*)bytes);  bytes += sizeof(int);
1992         ASLogDebug(@"SetWindowPositionMsgID: x=%d y=%d", winposX, winposY);
1993     } else {
1994         ASLogWarn(@"Unknown message received (msgid=%d)", msgid);
1995     }
1998 - (void)doKeyDown:(NSString *)key
1999           keyCode:(unsigned)code
2000         modifiers:(int)mods
2002     ASLogDebug(@"key='%@' code=%#x mods=%#x length=%d", key, code, mods,
2003             [key length]);
2004     if (!key) return;
2006     char_u *str = (char_u*)[key UTF8String];
2007     int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2009     if ([self handleSpecialKey:key keyCode:code modifiers:mods])
2010         return;
2012 #ifdef FEAT_MBYTE
2013     char_u *conv_str = NULL;
2014     if (input_conv.vc_type != CONV_NONE) {
2015         conv_str = string_convert(&input_conv, str, &len);
2016         if (conv_str)
2017             str = conv_str;
2018     }
2019 #endif
2021     if (mods & MOD_MASK_CMD) {
2022         // NOTE: For normal input (non-special, 'macmeta' off) the modifier
2023         // flags are already included in the key event.  However, the Cmd key
2024         // flag is special and must always be added manually.
2025         // The Shift flag is already included in the key when the Command
2026         // key is held.  The same goes for Alt, unless Ctrl is held or
2027         // 'macmeta' is set.  It is important that these flags are cleared
2028         // _after_ special keys have been handled, since they should never be
2029         // cleared for special keys.
2030         mods &= ~MOD_MASK_SHIFT;
2031         if (!(mods & MOD_MASK_CTRL)) {
2032             BOOL mmta = curbuf ? curbuf->b_p_mmta : YES;
2033             if (!mmta)
2034                 mods &= ~MOD_MASK_ALT;
2035         }
2037         ASLogDebug(@"add mods=%#x", mods);
2038         char_u modChars[3] = { CSI, KS_MODIFIER, mods };
2039         add_to_input_buf(modChars, 3);
2040     } else if (mods & MOD_MASK_ALT && 1 == len && str[0] < 0x80
2041             && curbuf && curbuf->b_p_mmta) {
2042         // HACK! The 'macmeta' is set so we have to handle Alt key presses
2043         // separately.  Normally Alt key presses are interpreted by the
2044         // frontend but now we have to manually set the 8th bit and deal with
2045         // UTF-8 conversion.
2046         if ([self handleMacMetaKey:str[0] modifiers:mods])
2047             return;
2048     }
2051     for (i = 0; i < len; ++i) {
2052         ASLogDebug(@"add byte [%d/%d]: %#x", i, len, str[i]);
2053         add_to_input_buf(str+i, 1);
2054         if (CSI == str[i]) {
2055             // NOTE: If the converted string contains the byte CSI, then it
2056             // must be followed by the bytes KS_EXTRA, KE_CSI or things
2057             // won't work.
2058             static char_u extra[2] = { KS_EXTRA, KE_CSI };
2059             ASLogDebug(@"add KS_EXTRA, KE_CSI");
2060             add_to_input_buf(extra, 2);
2061         }
2062     }
2064 #ifdef FEAT_MBYTE
2065     if (conv_str)
2066         vim_free(conv_str);
2067 #endif
2070 - (BOOL)handleSpecialKey:(NSString *)key
2071                  keyCode:(unsigned)code
2072                modifiers:(int)mods
2074     int i;
2075     for (i = 0; special_keys[i].key_sym != 0; i++) {
2076         if (special_keys[i].key_sym == code) {
2077             ASLogDebug(@"Special key: %#x", code);
2078             break;
2079         }
2080     }
2081     if (special_keys[i].key_sym == 0)
2082         return NO;
2084     int ikey = special_keys[i].vim_code1 == NUL ? special_keys[i].vim_code0 :
2085             TO_SPECIAL(special_keys[i].vim_code0, special_keys[i].vim_code1);
2086     ikey = simplify_key(ikey, &mods);
2087     if (ikey == CSI)
2088         ikey = K_CSI;
2090     char_u chars[4];
2091     int len = 0;
2093     if (IS_SPECIAL(ikey)) {
2094         chars[0] = CSI;
2095         chars[1] = K_SECOND(ikey);
2096         chars[2] = K_THIRD(ikey);
2097         len = 3;
2098     } else if (mods & MOD_MASK_ALT && special_keys[i].vim_code1 == 0
2099 #ifdef FEAT_MBYTE
2100             && !enc_dbcs    // TODO: ?  (taken from gui_gtk_x11.c)
2101 #endif
2102             ) {
2103         ASLogDebug(@"Alt special=%d", ikey);
2105         // NOTE: The last entries in the special_keys struct when pressed
2106         // together with Alt need to be handled separately or they will not
2107         // work.
2108         // The following code was gleaned from gui_gtk_x11.c.
2109         mods &= ~MOD_MASK_ALT;
2110         int mkey = 0x80 | ikey;
2111 #ifdef FEAT_MBYTE
2112         if (enc_utf8) {  // TODO: What about other encodings?
2113             // Convert to utf-8
2114             chars[0] = (mkey >> 6) + 0xc0;
2115             chars[1] = mkey & 0xbf;
2116             if (chars[1] == CSI) {
2117                 // We end up here when ikey == ESC
2118                 chars[2] = KS_EXTRA;
2119                 chars[3] = KE_CSI;
2120                 len = 4;
2121             } else {
2122                 len = 2;
2123             }
2124         } else
2125 #endif
2126         {
2127             chars[0] = mkey;
2128             len = 1;
2129         }
2130     } else {
2131         ASLogDebug(@"Just ikey=%d", ikey);
2132         chars[0] = ikey;
2133         len = 1;
2134     }
2136     if (len > 0) {
2137         if (mods) {
2138             ASLogDebug(@"Adding mods to special: %d", mods);
2139             char_u modChars[3] = { CSI, KS_MODIFIER, (char_u)mods };
2140             add_to_input_buf(modChars, 3);
2141         }
2143         ASLogDebug(@"Adding special (%d): %x,%x,%x", len,
2144                 chars[0], chars[1], chars[2]);
2145         add_to_input_buf(chars, len);
2146     }
2148     return YES;
2151 - (BOOL)handleMacMetaKey:(int)ikey modifiers:(int)mods
2153     ASLogDebug(@"ikey=%d mods=%d", ikey, mods);
2155     // This code was taken from gui_w48.c and gui_gtk_x11.c.
2156     char_u string[7];
2157     int ch = simplify_key(ikey, &mods);
2159     // Remove the SHIFT modifier for keys where it's already included,
2160     // e.g., '(' and '*'
2161     if (ch < 0x100 && !isalpha(ch) && isprint(ch))
2162         mods &= ~MOD_MASK_SHIFT;
2164     // Interpret the ALT key as making the key META, include SHIFT, etc.
2165     ch = extract_modifiers(ch, &mods);
2166     if (ch == CSI)
2167         ch = K_CSI;
2169     int len = 0;
2170     if (mods) {
2171         string[len++] = CSI;
2172         string[len++] = KS_MODIFIER;
2173         string[len++] = mods;
2174     }
2176     string[len++] = ch;
2177 #ifdef FEAT_MBYTE
2178     // TODO: What if 'enc' is not "utf-8"?
2179     if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
2180         string[len++] = ch & 0xbf;
2181         string[len-2] = ((unsigned)ch >> 6) + 0xc0;
2182         if (string[len-1] == CSI) {
2183             string[len++] = KS_EXTRA;
2184             string[len++] = (int)KE_CSI;
2185         }
2186     }
2187 #endif
2189     add_to_input_buf(string, len);
2190     return YES;
2193 - (void)queueMessage:(int)msgid data:(NSData *)data
2195     [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2196     if (data)
2197         [outputQueue addObject:data];
2198     else
2199         [outputQueue addObject:[NSData data]];
2202 - (void)connectionDidDie:(NSNotification *)notification
2204     // If the main connection to MacVim is lost this means that either MacVim
2205     // has crashed or this process did not receive its termination message
2206     // properly (e.g. if the TerminateNowMsgID was dropped).
2207     //
2208     // NOTE: This is not called if a Vim controller invalidates its connection.
2210     ASLogNotice(@"Main connection was lost before process had a chance "
2211                 "to terminate; preserving swap files.");
2212     getout_preserve_modified(1);
2215 - (void)blinkTimerFired:(NSTimer *)timer
2217     NSTimeInterval timeInterval = 0;
2219     [blinkTimer release];
2220     blinkTimer = nil;
2222     if (MMBlinkStateOn == blinkState) {
2223         gui_undraw_cursor();
2224         blinkState = MMBlinkStateOff;
2225         timeInterval = blinkOffInterval;
2226     } else if (MMBlinkStateOff == blinkState) {
2227         gui_update_cursor(TRUE, FALSE);
2228         blinkState = MMBlinkStateOn;
2229         timeInterval = blinkOnInterval;
2230     }
2232     if (timeInterval > 0) {
2233         blinkTimer = 
2234             [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2235                                             selector:@selector(blinkTimerFired:)
2236                                             userInfo:nil repeats:NO] retain];
2237         [self flushQueue:YES];
2238     }
2241 - (void)focusChange:(BOOL)on
2243     gui_focus_change(on);
2246 - (void)handleToggleToolbar
2248     // If 'go' contains 'T', then remove it, else add it.
2250     char_u go[sizeof(GO_ALL)+2];
2251     char_u *p;
2252     int len;
2254     STRCPY(go, p_go);
2255     p = vim_strchr(go, GO_TOOLBAR);
2256     len = STRLEN(go);
2258     if (p != NULL) {
2259         char_u *end = go + len;
2260         while (p < end) {
2261             p[0] = p[1];
2262             ++p;
2263         }
2264     } else {
2265         go[len] = GO_TOOLBAR;
2266         go[len+1] = NUL;
2267     }
2269     set_option_value((char_u*)"guioptions", 0, go, 0);
2272 - (void)handleScrollbarEvent:(NSData *)data
2274     if (!data) return;
2276     const void *bytes = [data bytes];
2277     int32_t ident = *((int32_t*)bytes);  bytes += sizeof(int32_t);
2278     int hitPart = *((int*)bytes);  bytes += sizeof(int);
2279     float fval = *((float*)bytes);  bytes += sizeof(float);
2280     scrollbar_T *sb = gui_find_scrollbar(ident);
2282     if (sb) {
2283         scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2284         long value = sb_info->value;
2285         long size = sb_info->size;
2286         long max = sb_info->max;
2287         BOOL isStillDragging = NO;
2288         BOOL updateKnob = YES;
2290         switch (hitPart) {
2291         case NSScrollerDecrementPage:
2292             value -= (size > 2 ? size - 2 : 1);
2293             break;
2294         case NSScrollerIncrementPage:
2295             value += (size > 2 ? size - 2 : 1);
2296             break;
2297         case NSScrollerDecrementLine:
2298             --value;
2299             break;
2300         case NSScrollerIncrementLine:
2301             ++value;
2302             break;
2303         case NSScrollerKnob:
2304             isStillDragging = YES;
2305             // fall through ...
2306         case NSScrollerKnobSlot:
2307             value = (long)(fval * (max - size + 1));
2308             // fall through ...
2309         default:
2310             updateKnob = NO;
2311             break;
2312         }
2314         gui_drag_scrollbar(sb, value, isStillDragging);
2316         if (updateKnob) {
2317             // Dragging the knob or option+clicking automatically updates
2318             // the knob position (on the actual NSScroller), so we only
2319             // need to set the knob position in the other cases.
2320             if (sb->wp) {
2321                 // Update both the left&right vertical scrollbars.
2322                 int32_t idL = (int32_t)sb->wp->w_scrollbars[SBAR_LEFT].ident;
2323                 int32_t idR = (int32_t)sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2324                 [self setScrollbarThumbValue:value size:size max:max
2325                                   identifier:idL];
2326                 [self setScrollbarThumbValue:value size:size max:max
2327                                   identifier:idR];
2328             } else {
2329                 // Update the horizontal scrollbar.
2330                 [self setScrollbarThumbValue:value size:size max:max
2331                                   identifier:ident];
2332             }
2333         }
2334     }
2337 - (void)handleSetFont:(NSData *)data
2339     if (!data) return;
2341     const void *bytes = [data bytes];
2342     int pointSize = (int)*((float*)bytes);  bytes += sizeof(float);
2344     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2345     NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2346     bytes += len;
2348     [name appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2349     char_u *s = (char_u*)[name UTF8String];
2351     unsigned wlen = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2352     char_u *ws = NULL;
2353     if (wlen > 0) {
2354         NSMutableString *wname = [NSMutableString stringWithUTF8String:bytes];
2355         bytes += wlen;
2357         [wname appendString:[NSString stringWithFormat:@":h%d", pointSize]];
2358         ws = (char_u*)[wname UTF8String];
2359     }
2361 #ifdef FEAT_MBYTE
2362     s = CONVERT_FROM_UTF8(s);
2363     if (ws) {
2364         ws = CONVERT_FROM_UTF8(ws);
2365     }
2366 #endif
2368     set_option_value((char_u*)"guifont", 0, s, 0);
2370     if (ws && gui.wide_font != NOFONT) {
2371         // NOTE: This message is sent on Cmd-+/Cmd-- and as such should only
2372         // change the wide font if 'gfw' is non-empty (the frontend always has
2373         // some wide font set, even if 'gfw' is empty).
2374         set_option_value((char_u*)"guifontwide", 0, ws, 0);
2375     }
2377 #ifdef FEAT_MBYTE
2378     if (ws) {
2379         CONVERT_FROM_UTF8_FREE(ws);
2380     }
2381     CONVERT_FROM_UTF8_FREE(s);
2382 #endif
2384     [self redrawScreen];
2387 - (void)handleDropFiles:(NSData *)data
2389     // TODO: Get rid of this method; instead use Vim script directly.  At the
2390     // moment I know how to do this to open files in tabs, but I'm not sure how
2391     // to add the filenames to the command line when in command line mode.
2393     if (!data) return;
2395     NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2396     if (!args) return;
2398     id obj = [args objectForKey:@"forceOpen"];
2399     BOOL forceOpen = YES;
2400     if (obj)
2401         forceOpen = [obj boolValue];
2403     NSArray *filenames = [args objectForKey:@"filenames"];
2404     if (!(filenames && [filenames count] > 0)) return;
2406 #ifdef FEAT_DND
2407     if (!forceOpen && (State & CMDLINE)) {
2408         // HACK!  If Vim is in command line mode then the files names
2409         // should be added to the command line, instead of opening the
2410         // files in tabs (unless forceOpen is set).  This is taken care of by
2411         // gui_handle_drop().
2412         int n = [filenames count];
2413         char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2414         if (fnames) {
2415             int i = 0;
2416             for (i = 0; i < n; ++i)
2417                 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2419             // NOTE!  This function will free 'fnames'.
2420             // HACK!  It is assumed that the 'x' and 'y' arguments are
2421             // unused when in command line mode.
2422             gui_handle_drop(0, 0, 0, fnames, n);
2423         }
2424     } else
2425 #endif // FEAT_DND
2426     {
2427         [self handleOpenWithArguments:args];
2428     }
2431 - (void)handleDropString:(NSData *)data
2433     if (!data) return;
2435 #ifdef FEAT_DND
2436     char_u  dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2437     const void *bytes = [data bytes];
2438     int len = *((int*)bytes);  bytes += sizeof(int);
2439     NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2441     // Replace unrecognized end-of-line sequences with \x0a (line feed).
2442     NSRange range = { 0, [string length] };
2443     unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2444                                          withString:@"\x0a" options:0
2445                                               range:range];
2446     if (0 == n) {
2447         n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2448                                        options:0 range:range];
2449     }
2451     len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2452     char_u *s = (char_u*)[string UTF8String];
2453 #ifdef FEAT_MBYTE
2454     if (input_conv.vc_type != CONV_NONE)
2455         s = string_convert(&input_conv, s, &len);
2456 #endif
2457     dnd_yank_drag_data(s, len);
2458 #ifdef FEAT_MBYTE
2459     if (input_conv.vc_type != CONV_NONE)
2460         vim_free(s);
2461 #endif
2462     add_to_input_buf(dropkey, sizeof(dropkey));
2463 #endif // FEAT_DND
2466 - (void)startOdbEditWithArguments:(NSDictionary *)args
2468 #ifdef FEAT_ODB_EDITOR
2469     id obj = [args objectForKey:@"remoteID"];
2470     if (!obj) return;
2472     OSType serverID = [obj unsignedIntValue];
2473     NSString *remotePath = [args objectForKey:@"remotePath"];
2475     NSAppleEventDescriptor *token = nil;
2476     NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2477     obj = [args objectForKey:@"remoteTokenDescType"];
2478     if (tokenData && obj) {
2479         DescType tokenType = [obj unsignedLongValue];
2480         token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2481                                                                 data:tokenData];
2482     }
2484     NSArray *filenames = [args objectForKey:@"filenames"];
2485     unsigned i, numFiles = [filenames count];
2486     for (i = 0; i < numFiles; ++i) {
2487         NSString *filename = [filenames objectAtIndex:i];
2488         char_u *s = [filename vimStringSave];
2489         buf_T *buf = buflist_findname(s);
2490         vim_free(s);
2492         if (buf) {
2493             if (buf->b_odb_token) {
2494                 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2495                 buf->b_odb_token = NULL;
2496             }
2498             if (buf->b_odb_fname) {
2499                 vim_free(buf->b_odb_fname);
2500                 buf->b_odb_fname = NULL;
2501             }
2503             buf->b_odb_server_id = serverID;
2505             if (token)
2506                 buf->b_odb_token = [token retain];
2507             if (remotePath)
2508                 buf->b_odb_fname = [remotePath vimStringSave];
2509         } else {
2510             ASLogWarn(@"Could not find buffer '%@' for ODB editing.", filename);
2511         }
2512     }
2513 #endif // FEAT_ODB_EDITOR
2516 - (void)handleXcodeMod:(NSData *)data
2518 #if 0
2519     const void *bytes = [data bytes];
2520     DescType type = *((DescType*)bytes);  bytes += sizeof(DescType);
2521     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2522     if (0 == len)
2523         return;
2525     NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2526             descriptorWithDescriptorType:type
2527                                    bytes:bytes
2528                                   length:len];
2529 #endif
2532 - (void)handleOpenWithArguments:(NSDictionary *)args
2534     // ARGUMENT:                DESCRIPTION:
2535     // -------------------------------------------------------------
2536     // filenames                list of filenames
2537     // dontOpen                 don't open files specified in above argument
2538     // layout                   which layout to use to open files
2539     // selectionRange           range of lines to select
2540     // searchText               string to search for
2541     // cursorLine               line to position the cursor on
2542     // cursorColumn             column to position the cursor on
2543     //                          (only valid when "cursorLine" is set)
2544     // remoteID                 ODB parameter
2545     // remotePath               ODB parameter
2546     // remoteTokenDescType      ODB parameter
2547     // remoteTokenData          ODB parameter
2549     ASLogDebug(@"args=%@ (starting=%d)", args, starting);
2551     NSArray *filenames = [args objectForKey:@"filenames"];
2552     int i, numFiles = filenames ? [filenames count] : 0;
2553     BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2554     int layout = [[args objectForKey:@"layout"] intValue];
2556     // Change to directory of first file to open if this is an "unused" editor
2557     // (but do not do this if editing remotely).
2558     if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2559             && (starting || [self unusedEditor]) ) {
2560         char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2561         if (mch_isdir(s)) {
2562             mch_chdir((char*)s);
2563         } else {
2564             vim_chdirfile(s);
2565         }
2566         vim_free(s);
2567     }
2569     if (starting > 0) {
2570         // When Vim is starting we simply add the files to be opened to the
2571         // global arglist and Vim will take care of opening them for us.
2572         if (openFiles && numFiles > 0) {
2573             for (i = 0; i < numFiles; i++) {
2574                 NSString *fname = [filenames objectAtIndex:i];
2575                 char_u *p = NULL;
2577                 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2578                         || (p = [fname vimStringSave]) == NULL)
2579                     exit(2); // See comment in -[MMBackend exit]
2580                 else
2581                     alist_add(&global_alist, p, 2);
2582             }
2584             // Vim will take care of arranging the files added to the arglist
2585             // in windows or tabs; all we must do is to specify which layout to
2586             // use.
2587             initialWindowLayout = layout;
2588         }
2589     } else {
2590         // When Vim is already open we resort to some trickery to open the
2591         // files with the specified layout.
2592         //
2593         // TODO: Figure out a better way to handle this?
2594         if (openFiles && numFiles > 0) {
2595             BOOL oneWindowInTab = topframe ? YES
2596                                            : (topframe->fr_layout == FR_LEAF);
2597             BOOL bufChanged = NO;
2598             BOOL bufHasFilename = NO;
2599             if (curbuf) {
2600                 bufChanged = curbufIsChanged();
2601                 bufHasFilename = curbuf->b_ffname != NULL;
2602             }
2604             // Temporarily disable flushing since the following code may
2605             // potentially cause multiple redraws.
2606             flushDisabled = YES;
2608             BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2609             if (WIN_TABS == layout && !onlyOneTab) {
2610                 // By going to the last tabpage we ensure that the new tabs
2611                 // will appear last (if this call is left out, the taborder
2612                 // becomes messy).
2613                 goto_tabpage(9999);
2614             }
2616             // Make sure we're in normal mode first.
2617             [self addInput:@"<C-\\><C-N>"];
2619             if (numFiles > 1) {
2620                 // With "split layout" we open a new tab before opening
2621                 // multiple files if the current tab has more than one window
2622                 // or if there is exactly one window but whose buffer has a
2623                 // filename.  (The :drop command ensures modified buffers get
2624                 // their own window.)
2625                 if ((WIN_HOR == layout || WIN_VER == layout) &&
2626                         (!oneWindowInTab || bufHasFilename))
2627                     [self addInput:@":tabnew<CR>"];
2629                 // The files are opened by constructing a ":drop ..." command
2630                 // and executing it.
2631                 NSMutableString *cmd = (WIN_TABS == layout)
2632                         ? [NSMutableString stringWithString:@":tab drop"]
2633                         : [NSMutableString stringWithString:@":drop"];
2635                 for (i = 0; i < numFiles; ++i) {
2636                     NSString *file = [filenames objectAtIndex:i];
2637                     file = [file stringByEscapingSpecialFilenameCharacters];
2638                     [cmd appendString:@" "];
2639                     [cmd appendString:file];
2640                 }
2642                 // Temporarily clear 'suffixes' so that the files are opened in
2643                 // the same order as they appear in the "filenames" array.
2644                 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2646                 [self addInput:cmd];
2648                 // Split the view into multiple windows if requested.
2649                 if (WIN_HOR == layout)
2650                     [self addInput:@"|sall"];
2651                 else if (WIN_VER == layout)
2652                     [self addInput:@"|vert sall"];
2654                 // Restore the old value of 'suffixes'.
2655                 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2656             } else {
2657                 // When opening one file we try to reuse the current window,
2658                 // but not if its buffer is modified or has a filename.
2659                 // However, the 'arglist' layout always opens the file in the
2660                 // current window.
2661                 NSString *file = [[filenames lastObject]
2662                         stringByEscapingSpecialFilenameCharacters];
2663                 NSString *cmd;
2664                 if (WIN_HOR == layout) {
2665                     if (!(bufHasFilename || bufChanged))
2666                         cmd = [NSString stringWithFormat:@":e %@", file];
2667                     else
2668                         cmd = [NSString stringWithFormat:@":sp %@", file];
2669                 } else if (WIN_VER == layout) {
2670                     if (!(bufHasFilename || bufChanged))
2671                         cmd = [NSString stringWithFormat:@":e %@", file];
2672                     else
2673                         cmd = [NSString stringWithFormat:@":vsp %@", file];
2674                 } else if (WIN_TABS == layout) {
2675                     if (oneWindowInTab && !(bufHasFilename || bufChanged))
2676                         cmd = [NSString stringWithFormat:@":e %@", file];
2677                     else
2678                         cmd = [NSString stringWithFormat:@":tabe %@", file];
2679                 } else {
2680                     // (The :drop command will split if there is a modified
2681                     // buffer.)
2682                     cmd = [NSString stringWithFormat:@":drop %@", file];
2683                 }
2685                 [self addInput:cmd];
2686                 [self addInput:@"<CR>"];
2687             }
2689             // Force screen redraw (does it have to be this complicated?).
2690             // (This code was taken from the end of gui_handle_drop().)
2691             update_screen(NOT_VALID);
2692             setcursor();
2693             out_flush();
2694             gui_update_cursor(FALSE, FALSE);
2695             maketitle();
2697             flushDisabled = NO;
2698         }
2699     }
2701     if ([args objectForKey:@"remoteID"]) {
2702         // NOTE: We have to delay processing any ODB related arguments since
2703         // the file(s) may not be opened until the input buffer is processed.
2704         [self performSelector:@selector(startOdbEditWithArguments:)
2705                    withObject:args
2706                    afterDelay:0];
2707     }
2709     NSString *lineString = [args objectForKey:@"cursorLine"];
2710     if (lineString && [lineString intValue] > 0) {
2711         NSString *columnString = [args objectForKey:@"cursorColumn"];
2712         if (!(columnString && [columnString intValue] > 0))
2713             columnString = @"1";
2715         NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2716                 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2717         [self addInput:cmd];
2718     }
2720     NSString *rangeString = [args objectForKey:@"selectionRange"];
2721     if (rangeString) {
2722         // Build a command line string that will select the given range of
2723         // lines.  If range.length == 0, then position the cursor on the given
2724         // line but do not select.
2725         NSRange range = NSRangeFromString(rangeString);
2726         NSString *cmd;
2727         if (range.length > 0) {
2728             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2729                     NSMaxRange(range), range.location];
2730         } else {
2731             cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2732                     range.location];
2733         }
2735         [self addInput:cmd];
2736     }
2738     NSString *searchText = [args objectForKey:@"searchText"];
2739     if (searchText) {
2740         [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2741                 searchText]];
2742     }
2745 - (BOOL)checkForModifiedBuffers
2747     buf_T *buf;
2748     for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2749         if (bufIsChanged(buf)) {
2750             return YES;
2751         }
2752     }
2754     return NO;
2757 - (void)addInput:(NSString *)input
2759     // NOTE: This code is essentially identical to server_to_input_buf(),
2760     // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2761     char_u *string = [input vimStringSave];
2762     if (!string) return;
2764     /* Set 'cpoptions' the way we want it.
2765      *    B set - backslashes are *not* treated specially
2766      *    k set - keycodes are *not* reverse-engineered
2767      *    < unset - <Key> sequences *are* interpreted
2768      *  The last but one parameter of replace_termcodes() is TRUE so that the
2769      *  <lt> sequence is recognised - needed for a real backslash.
2770      */
2771     char_u *ptr = NULL;
2772     char_u *cpo_save = p_cpo;
2773     p_cpo = (char_u *)"Bk";
2774     char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2775     p_cpo = cpo_save;
2777     if (*ptr != NUL)    /* trailing CTRL-V results in nothing */
2778     {
2779         /*
2780          * Add the string to the input stream.
2781          * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2782          *
2783          * First clear typed characters from the typeahead buffer, there could
2784          * be half a mapping there.  Then append to the existing string, so
2785          * that multiple commands from a client are concatenated.
2786          */
2787         if (typebuf.tb_maplen < typebuf.tb_len)
2788             del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2789         (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2791         /* Let input_available() know we inserted text in the typeahead
2792          * buffer. */
2793         typebuf_was_filled = TRUE;
2794     }
2795     vim_free(ptr);
2796     vim_free(string);
2799 - (BOOL)unusedEditor
2801     BOOL oneWindowInTab = topframe ? YES
2802                                    : (topframe->fr_layout == FR_LEAF);
2803     BOOL bufChanged = NO;
2804     BOOL bufHasFilename = NO;
2805     if (curbuf) {
2806         bufChanged = curbufIsChanged();
2807         bufHasFilename = curbuf->b_ffname != NULL;
2808     }
2810     BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2812     return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2815 - (void)redrawScreen
2817     // Force screen redraw (does it have to be this complicated?).
2818     redraw_all_later(CLEAR);
2819     update_screen(NOT_VALID);
2820     setcursor();
2821     out_flush();
2822     gui_update_cursor(FALSE, FALSE);
2824     // HACK! The cursor is not put back at the command line by the above
2825     // "redraw commands".  The following test seems to do the trick though.
2826     if (State & CMDLINE)
2827         redrawcmdline();
2830 - (void)handleFindReplace:(NSDictionary *)args
2832     if (!args) return;
2834     NSString *findString = [args objectForKey:@"find"];
2835     if (!findString) return;
2837     char_u *find = [findString vimStringSave];
2838     char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2839     int flags = [[args objectForKey:@"flags"] intValue];
2841     // NOTE: The flag 0x100 is used to indicate a backward search.
2842     gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2844     vim_free(find);
2845     vim_free(replace);
2849 - (void)handleMarkedText:(NSData *)data
2851     const void *bytes = [data bytes];
2852     int32_t pos = *((int32_t*)bytes);  bytes += sizeof(int32_t);
2853     unsigned len = *((unsigned*)bytes);  bytes += sizeof(unsigned);
2854     char *chars = (char *)bytes;
2856     ASLogDebug(@"pos=%d len=%d chars=%s", pos, len, chars);
2858     if (pos < 0) {
2859         im_preedit_abandon_macvim();
2860     } else if (len == 0) {
2861         im_preedit_end_macvim();
2862     } else {
2863         if (!preedit_get_status())
2864             im_preedit_start_macvim();
2866         im_preedit_changed_macvim(chars, pos);
2867     }
2870 @end // MMBackend (Private)
2875 @implementation MMBackend (ClientServer)
2877 - (NSString *)connectionNameFromServerName:(NSString *)name
2879     NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2881     return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2882         lowercaseString];
2885 - (NSConnection *)connectionForServerName:(NSString *)name
2887     // TODO: Try 'name%d' if 'name' fails.
2888     NSString *connName = [self connectionNameFromServerName:name];
2889     NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2891     if (!svrConn) {
2892         svrConn = [NSConnection connectionWithRegisteredName:connName
2893                                                            host:nil];
2894         // Try alternate server...
2895         if (!svrConn && alternateServerName) {
2896             ASLogInfo(@"  trying to connect to alternate server: %@",
2897                       alternateServerName);
2898             connName = [self connectionNameFromServerName:alternateServerName];
2899             svrConn = [NSConnection connectionWithRegisteredName:connName
2900                                                             host:nil];
2901         }
2903         // Try looking for alternate servers...
2904         if (!svrConn) {
2905             ASLogInfo(@"  looking for alternate servers...");
2906             NSString *alt = [self alternateServerNameForName:name];
2907             if (alt != alternateServerName) {
2908                 ASLogInfo(@"  found alternate server: %@", alt);
2909                 [alternateServerName release];
2910                 alternateServerName = [alt copy];
2911             }
2912         }
2914         // Try alternate server again...
2915         if (!svrConn && alternateServerName) {
2916             ASLogInfo(@"  trying to connect to alternate server: %@",
2917                       alternateServerName);
2918             connName = [self connectionNameFromServerName:alternateServerName];
2919             svrConn = [NSConnection connectionWithRegisteredName:connName
2920                                                             host:nil];
2921         }
2923         if (svrConn) {
2924             [connectionNameDict setObject:svrConn forKey:connName];
2926             ASLogDebug(@"Adding %@ as connection observer for %@",
2927                        self, svrConn);
2928             [[NSNotificationCenter defaultCenter] addObserver:self
2929                     selector:@selector(serverConnectionDidDie:)
2930                         name:NSConnectionDidDieNotification object:svrConn];
2931         }
2932     }
2934     return svrConn;
2937 - (NSConnection *)connectionForServerPort:(int)port
2939     NSConnection *conn;
2940     NSEnumerator *e = [connectionNameDict objectEnumerator];
2942     while ((conn = [e nextObject])) {
2943         // HACK! Assume connection uses mach ports.
2944         if (port == [(NSMachPort*)[conn sendPort] machPort])
2945             return conn;
2946     }
2948     return nil;
2951 - (void)serverConnectionDidDie:(NSNotification *)notification
2953     ASLogDebug(@"notification=%@", notification);
2955     NSConnection *svrConn = [notification object];
2957     ASLogDebug(@"Removing %@ as connection observer from %@", self, svrConn);
2958     [[NSNotificationCenter defaultCenter]
2959             removeObserver:self
2960                       name:NSConnectionDidDieNotification
2961                     object:svrConn];
2963     [connectionNameDict removeObjectsForKeys:
2964         [connectionNameDict allKeysForObject:svrConn]];
2966     // HACK! Assume connection uses mach ports.
2967     int port = [(NSMachPort*)[svrConn sendPort] machPort];
2968     NSNumber *key = [NSNumber numberWithInt:port];
2970     [clientProxyDict removeObjectForKey:key];
2971     [serverReplyDict removeObjectForKey:key];
2974 - (void)addClient:(NSDistantObject *)client
2976     NSConnection *conn = [client connectionForProxy];
2977     // HACK! Assume connection uses mach ports.
2978     int port = [(NSMachPort*)[conn sendPort] machPort];
2979     NSNumber *key = [NSNumber numberWithInt:port];
2981     if (![clientProxyDict objectForKey:key]) {
2982         [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2983         [clientProxyDict setObject:client forKey:key];
2984     }
2986     // NOTE: 'clientWindow' is a global variable which is used by <client>
2987     clientWindow = port;
2990 - (NSString *)alternateServerNameForName:(NSString *)name
2992     if (!(name && [name length] > 0))
2993         return nil;
2995     // Only look for alternates if 'name' doesn't end in a digit.
2996     unichar lastChar = [name characterAtIndex:[name length]-1];
2997     if (lastChar >= '0' && lastChar <= '9')
2998         return nil;
3000     // Look for alternates among all current servers.
3001     NSArray *list = [self serverList];
3002     if (!(list && [list count] > 0))
3003         return nil;
3005     // Filter out servers starting with 'name' and ending with a number. The
3006     // (?i) pattern ensures that the match is case insensitive.
3007     NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
3008     NSPredicate *pred = [NSPredicate predicateWithFormat:
3009             @"SELF MATCHES %@", pat];
3010     list = [list filteredArrayUsingPredicate:pred];
3011     if ([list count] > 0) {
3012         list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
3013         return [list objectAtIndex:0];
3014     }
3016     return nil;
3019 @end // MMBackend (ClientServer)
3024 @implementation NSString (MMServerNameCompare)
3025 - (NSComparisonResult)serverNameCompare:(NSString *)string
3027     return [self compare:string
3028                  options:NSCaseInsensitiveSearch|NSNumericSearch];
3030 @end
3035 static int eventModifierFlagsToVimModMask(int modifierFlags)
3037     int modMask = 0;
3039     if (modifierFlags & NSShiftKeyMask)
3040         modMask |= MOD_MASK_SHIFT;
3041     if (modifierFlags & NSControlKeyMask)
3042         modMask |= MOD_MASK_CTRL;
3043     if (modifierFlags & NSAlternateKeyMask)
3044         modMask |= MOD_MASK_ALT;
3045     if (modifierFlags & NSCommandKeyMask)
3046         modMask |= MOD_MASK_CMD;
3048     return modMask;
3051 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
3053     int modMask = 0;
3055     if (modifierFlags & NSShiftKeyMask)
3056         modMask |= MOUSE_SHIFT;
3057     if (modifierFlags & NSControlKeyMask)
3058         modMask |= MOUSE_CTRL;
3059     if (modifierFlags & NSAlternateKeyMask)
3060         modMask |= MOUSE_ALT;
3062     return modMask;
3065 static int eventButtonNumberToVimMouseButton(int buttonNumber)
3067     static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
3069     return (buttonNumber >= 0 && buttonNumber < 3)
3070             ? mouseButton[buttonNumber] : -1;
3075 // This function is modeled after the VimToPython function found in if_python.c
3076 // NB This does a deep copy by value, it does not lookup references like the
3077 // VimToPython function does.  This is because I didn't want to deal with the
3078 // retain cycles that this would create, and we can cover 99% of the use cases
3079 // by ignoring it.  If we ever switch to using GC in MacVim then this
3080 // functionality can be implemented easily.
3081 static id vimToCocoa(typval_T * tv, int depth)
3083     id result = nil;
3084     id newObj = nil;
3087     // Avoid infinite recursion
3088     if (depth > 100) {
3089         return nil;
3090     }
3092     if (tv->v_type == VAR_STRING) {
3093         char_u * val = tv->vval.v_string;
3094         // val can be NULL if the string is empty
3095         if (!val) {
3096             result = [NSString string];
3097         } else {
3098 #ifdef FEAT_MBYTE
3099             val = CONVERT_TO_UTF8(val);
3100 #endif
3101             result = [NSString stringWithUTF8String:(char*)val];
3102 #ifdef FEAT_MBYTE
3103             CONVERT_TO_UTF8_FREE(val);
3104 #endif
3105         }
3106     } else if (tv->v_type == VAR_NUMBER) {
3107         // looks like sizeof(varnumber_T) is always <= sizeof(long)
3108         result = [NSNumber numberWithLong:(long)tv->vval.v_number];
3109     } else if (tv->v_type == VAR_LIST) {
3110         list_T * list = tv->vval.v_list;
3111         listitem_T * curr;
3113         NSMutableArray * arr = result = [NSMutableArray array];
3115         if (list != NULL) {
3116             for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
3117                 newObj = vimToCocoa(&curr->li_tv, depth + 1);
3118                 [arr addObject:newObj];
3119             }
3120         }
3121     } else if (tv->v_type == VAR_DICT) {
3122         NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
3124         if (tv->vval.v_dict != NULL) {
3125             hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
3126             int todo = ht->ht_used;
3127             hashitem_T * hi;
3128             dictitem_T * di;
3130             for (hi = ht->ht_array; todo > 0; ++hi) {
3131                 if (!HASHITEM_EMPTY(hi)) {
3132                     --todo;
3134                     di = dict_lookup(hi);
3135                     newObj = vimToCocoa(&di->di_tv, depth + 1);
3137                     char_u * keyval = hi->hi_key;
3138 #ifdef FEAT_MBYTE
3139                     keyval = CONVERT_TO_UTF8(keyval);
3140 #endif
3141                     NSString * key = [NSString stringWithUTF8String:(char*)keyval];
3142 #ifdef FEAT_MBYTE
3143                     CONVERT_TO_UTF8_FREE(keyval);
3144 #endif
3145                     [dict setObject:newObj forKey:key];
3146                 }
3147             }
3148         }
3149     } else { // only func refs should fall into this category?
3150         result = nil;
3151     }
3153     return result;
3157 // This function is modeled after eval_client_expr_to_string found in main.c
3158 // Returns nil if there was an error evaluating the expression, and writes a
3159 // message to errorStr.
3160 // TODO Get the error that occurred while evaluating the expression in vim
3161 // somehow.
3162 static id evalExprCocoa(NSString * expr, NSString ** errstr)
3165     char_u *s = (char_u*)[expr UTF8String];
3167 #ifdef FEAT_MBYTE
3168     s = CONVERT_FROM_UTF8(s);
3169 #endif
3171     int save_dbl = debug_break_level;
3172     int save_ro = redir_off;
3174     debug_break_level = -1;
3175     redir_off = 0;
3176     ++emsg_skip;
3178     typval_T * tvres = eval_expr(s, NULL);
3180     debug_break_level = save_dbl;
3181     redir_off = save_ro;
3182     --emsg_skip;
3184     setcursor();
3185     out_flush();
3187 #ifdef FEAT_MBYTE
3188     CONVERT_FROM_UTF8_FREE(s);
3189 #endif
3191 #ifdef FEAT_GUI
3192     if (gui.in_use)
3193         gui_update_cursor(FALSE, FALSE);
3194 #endif
3196     if (tvres == NULL) {
3197         free_tv(tvres);
3198         *errstr = @"Expression evaluation failed.";
3199     }
3201     id res = vimToCocoa(tvres, 1);
3203     free_tv(tvres);
3205     if (res == nil) {
3206         *errstr = @"Conversion to cocoa values failed.";
3207     }
3209     return res;
3214 @implementation NSString (VimStrings)
3216 + (id)stringWithVimString:(char_u *)s
3218     // This method ensures a non-nil string is returned.  If 's' cannot be
3219     // converted to a utf-8 string it is assumed to be latin-1.  If conversion
3220     // still fails an empty NSString is returned.
3221     NSString *string = nil;
3222     if (s) {
3223 #ifdef FEAT_MBYTE
3224         s = CONVERT_TO_UTF8(s);
3225 #endif
3226         string = [NSString stringWithUTF8String:(char*)s];
3227         if (!string) {
3228             // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
3229             // latin-1?
3230             string = [NSString stringWithCString:(char*)s
3231                                         encoding:NSISOLatin1StringEncoding];
3232         }
3233 #ifdef FEAT_MBYTE
3234         CONVERT_TO_UTF8_FREE(s);
3235 #endif
3236     }
3238     return string != nil ? string : [NSString string];
3241 - (char_u *)vimStringSave
3243     char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3245 #ifdef FEAT_MBYTE
3246     s = CONVERT_FROM_UTF8(s);
3247 #endif
3248     ret = vim_strsave(s);
3249 #ifdef FEAT_MBYTE
3250     CONVERT_FROM_UTF8_FREE(s);
3251 #endif
3253     return ret;
3256 @end // NSString (VimStrings)