1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
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.
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
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:.
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 [MMVimController processCommandQueue:].
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.
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);
55 vimmenu_T *menu_for_descriptor(NSArray *desc);
57 static id evalExprCocoa(NSString * expr, NSString ** errstr);
66 static NSString *MMSymlinkWarningString =
67 @"\n\n\tMost likely this is because you have symlinked directly to\n"
68 "\tthe Vim binary, which Cocoa does not allow. Please use an\n"
69 "\talias or the mvim shell script instead. If you have not used\n"
70 "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
73 extern GuiFont gui_mch_retain_font(GuiFont font);
77 @interface NSString (MMServerNameCompare)
78 - (NSComparisonResult)serverNameCompare:(NSString *)string;
84 @interface MMBackend (Private)
85 - (void)clearDrawData;
86 - (void)didChangeWholeLine;
87 - (void)waitForDialogReturn;
88 - (void)insertVimStateMessage;
89 - (void)processInputQueue;
90 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
91 + (NSDictionary *)specialKeys;
92 - (void)handleInsertText:(NSString *)text;
93 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
94 - (void)queueMessage:(int)msgid data:(NSData *)data;
95 - (void)connectionDidDie:(NSNotification *)notification;
96 - (void)blinkTimerFired:(NSTimer *)timer;
97 - (void)focusChange:(BOOL)on;
98 - (void)handleToggleToolbar;
99 - (void)handleScrollbarEvent:(NSData *)data;
100 - (void)handleSetFont:(NSData *)data;
101 - (void)handleDropFiles:(NSData *)data;
102 - (void)handleDropString:(NSData *)data;
103 - (void)startOdbEditWithArguments:(NSDictionary *)args;
104 - (void)handleXcodeMod:(NSData *)data;
105 - (void)handleOpenWithArguments:(NSDictionary *)args;
106 - (BOOL)checkForModifiedBuffers;
107 - (void)addInput:(NSString *)input;
108 - (BOOL)unusedEditor;
109 - (void)redrawScreen;
110 - (void)handleFindReplace:(NSDictionary *)args;
115 @interface MMBackend (ClientServer)
116 - (NSString *)connectionNameFromServerName:(NSString *)name;
117 - (NSConnection *)connectionForServerName:(NSString *)name;
118 - (NSConnection *)connectionForServerPort:(int)port;
119 - (void)serverConnectionDidDie:(NSNotification *)notification;
120 - (void)addClient:(NSDistantObject *)client;
121 - (NSString *)alternateServerNameForName:(NSString *)name;
126 @implementation MMBackend
128 + (MMBackend *)sharedInstance
130 static MMBackend *singleton = nil;
131 return singleton ? singleton : (singleton = [MMBackend new]);
137 if (!self) return nil;
139 outputQueue = [[NSMutableArray alloc] init];
140 inputQueue = [[NSMutableArray alloc] init];
141 drawData = [[NSMutableData alloc] initWithCapacity:1024];
142 connectionNameDict = [[NSMutableDictionary alloc] init];
143 clientProxyDict = [[NSMutableDictionary alloc] init];
144 serverReplyDict = [[NSMutableDictionary alloc] init];
146 NSBundle *mainBundle = [NSBundle mainBundle];
147 NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
149 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
151 path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
153 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
156 path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
158 actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
160 if (!(colorDict && sysColorDict && actionDict))
161 NSLog(@"ERROR: Failed to load dictionaries.%@",
162 MMSymlinkWarningString);
169 //NSLog(@"%@ %s", [self className], _cmd);
170 [[NSNotificationCenter defaultCenter] removeObserver:self];
172 gui_mch_free_font(oldWideFont); oldWideFont = NOFONT;
173 [blinkTimer release]; blinkTimer = nil;
174 [alternateServerName release]; alternateServerName = nil;
175 [serverReplyDict release]; serverReplyDict = nil;
176 [clientProxyDict release]; clientProxyDict = nil;
177 [connectionNameDict release]; connectionNameDict = nil;
178 [inputQueue release]; inputQueue = nil;
179 [outputQueue release]; outputQueue = nil;
180 [drawData release]; drawData = nil;
181 [frontendProxy release]; frontendProxy = nil;
182 [connection release]; connection = nil;
183 [actionDict release]; actionDict = nil;
184 [sysColorDict release]; sysColorDict = nil;
185 [colorDict release]; colorDict = nil;
190 - (void)setBackgroundColor:(int)color
192 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
195 - (void)setForegroundColor:(int)color
197 foregroundColor = MM_COLOR(color);
200 - (void)setSpecialColor:(int)color
202 specialColor = MM_COLOR(color);
205 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
207 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
208 defaultForegroundColor = MM_COLOR(fg);
210 NSMutableData *data = [NSMutableData data];
212 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
213 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
215 [self queueMessage:SetDefaultColorsMsgID data:data];
218 - (NSConnection *)connection
221 // NOTE! If the name of the connection changes here it must also be
222 // updated in MMAppController.m.
223 NSString *name = [NSString stringWithFormat:@"%@-connection",
224 [[NSBundle mainBundle] bundlePath]];
226 connection = [NSConnection connectionWithRegisteredName:name host:nil];
230 // NOTE: 'connection' may be nil here.
234 - (NSDictionary *)actionDict
239 - (int)initialWindowLayout
241 return initialWindowLayout;
244 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
246 [self queueMessage:msgid data:[props dictionaryAsData]];
251 if (![self connection]) {
253 // This is a preloaded process and as such should not cause the
254 // MacVim to be opened. We probably got here as a result of the
255 // user quitting MacVim while the process was preloading, so exit
257 // (Don't use mch_exit() since it assumes the process has properly
262 NSBundle *mainBundle = [NSBundle mainBundle];
267 // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
268 // the API to pass Apple Event parameters is broken on 10.4).
269 NSString *path = [mainBundle bundlePath];
270 status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
271 if (noErr == status) {
272 // Pass parameter to the 'Open' Apple Event that tells MacVim not
273 // to open an untitled window.
274 NSAppleEventDescriptor *desc =
275 [NSAppleEventDescriptor recordDescriptor];
276 [desc setParamDescriptor:
277 [NSAppleEventDescriptor descriptorWithBoolean:NO]
278 forKeyword:keyMMUntitledWindow];
280 LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
281 kLSLaunchDefaults, NULL };
282 status = LSOpenFromRefSpec(&spec, NULL);
285 if (noErr != status) {
286 NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
287 path, MMSymlinkWarningString);
291 // Launch MacVim using NSTask. For some reason the above code using
292 // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
293 // fails, the dock icon starts bouncing and never stops). It seems
294 // like rebuilding the Launch Services database takes care of this
295 // problem, but the NSTask way seems more stable so stick with it.
297 // NOTE! Using NSTask to launch the GUI has the negative side-effect
298 // that the GUI won't be activated (or raised) so there is a hack in
299 // MMAppController which raises the app when a new window is opened.
300 NSMutableArray *args = [NSMutableArray arrayWithObjects:
301 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
302 NSString *exeName = [[mainBundle infoDictionary]
303 objectForKey:@"CFBundleExecutable"];
304 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
306 NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
307 MMSymlinkWarningString);
311 [NSTask launchedTaskWithLaunchPath:path arguments:args];
314 // HACK! Poll the mach bootstrap server until it returns a valid
315 // connection to detect that MacVim has finished launching. Also set a
316 // time-out date so that we don't get stuck doing this forever.
317 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
318 while (![self connection] &&
319 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
320 [[NSRunLoop currentRunLoop]
321 runMode:NSDefaultRunLoopMode
322 beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
324 // NOTE: [self connection] will set 'connection' as a side-effect.
326 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
333 [[NSNotificationCenter defaultCenter] addObserver:self
334 selector:@selector(connectionDidDie:)
335 name:NSConnectionDidDieNotification object:connection];
337 id proxy = [connection rootProxy];
338 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
340 int pid = [[NSProcessInfo processInfo] processIdentifier];
342 frontendProxy = [proxy connectBackend:self pid:pid];
344 [frontendProxy retain];
345 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
349 @catch (NSException *e) {
350 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
356 - (BOOL)openGUIWindow
358 [self queueMessage:OpenWindowMsgID data:nil];
364 int type = ClearAllDrawType;
366 // Any draw commands in queue are effectively obsolete since this clearAll
367 // will negate any effect they have, therefore we may as well clear the
369 [self clearDrawData];
371 [drawData appendBytes:&type length:sizeof(int)];
374 - (void)clearBlockFromRow:(int)row1 column:(int)col1
375 toRow:(int)row2 column:(int)col2
377 int type = ClearBlockDrawType;
379 [drawData appendBytes:&type length:sizeof(int)];
381 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
382 [drawData appendBytes:&row1 length:sizeof(int)];
383 [drawData appendBytes:&col1 length:sizeof(int)];
384 [drawData appendBytes:&row2 length:sizeof(int)];
385 [drawData appendBytes:&col2 length:sizeof(int)];
388 - (void)deleteLinesFromRow:(int)row count:(int)count
389 scrollBottom:(int)bottom left:(int)left right:(int)right
391 int type = DeleteLinesDrawType;
393 [drawData appendBytes:&type length:sizeof(int)];
395 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
396 [drawData appendBytes:&row length:sizeof(int)];
397 [drawData appendBytes:&count length:sizeof(int)];
398 [drawData appendBytes:&bottom length:sizeof(int)];
399 [drawData appendBytes:&left length:sizeof(int)];
400 [drawData appendBytes:&right length:sizeof(int)];
402 if (left == 0 && right == gui.num_cols-1)
403 [self didChangeWholeLine];
406 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
407 cells:(int)cells flags:(int)flags
409 if (len <= 0 || cells <= 0) return;
411 int type = DrawStringDrawType;
413 [drawData appendBytes:&type length:sizeof(int)];
415 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
416 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
417 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
418 [drawData appendBytes:&row length:sizeof(int)];
419 [drawData appendBytes:&col length:sizeof(int)];
420 [drawData appendBytes:&cells length:sizeof(int)];
421 [drawData appendBytes:&flags length:sizeof(int)];
422 [drawData appendBytes:&len length:sizeof(int)];
423 [drawData appendBytes:s length:len];
426 - (void)insertLinesFromRow:(int)row count:(int)count
427 scrollBottom:(int)bottom left:(int)left right:(int)right
429 int type = InsertLinesDrawType;
431 [drawData appendBytes:&type length:sizeof(int)];
433 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
434 [drawData appendBytes:&row length:sizeof(int)];
435 [drawData appendBytes:&count length:sizeof(int)];
436 [drawData appendBytes:&bottom length:sizeof(int)];
437 [drawData appendBytes:&left length:sizeof(int)];
438 [drawData appendBytes:&right length:sizeof(int)];
440 if (left == 0 && right == gui.num_cols-1)
441 [self didChangeWholeLine];
444 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
445 fraction:(int)percent color:(int)color
447 int type = DrawCursorDrawType;
448 unsigned uc = MM_COLOR(color);
450 [drawData appendBytes:&type length:sizeof(int)];
452 [drawData appendBytes:&uc length:sizeof(unsigned)];
453 [drawData appendBytes:&row length:sizeof(int)];
454 [drawData appendBytes:&col length:sizeof(int)];
455 [drawData appendBytes:&shape length:sizeof(int)];
456 [drawData appendBytes:&percent length:sizeof(int)];
459 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
460 numColumns:(int)nc invert:(int)invert
462 int type = DrawInvertedRectDrawType;
463 [drawData appendBytes:&type length:sizeof(int)];
465 [drawData appendBytes:&row length:sizeof(int)];
466 [drawData appendBytes:&col length:sizeof(int)];
467 [drawData appendBytes:&nr length:sizeof(int)];
468 [drawData appendBytes:&nc length:sizeof(int)];
469 [drawData appendBytes:&invert length:sizeof(int)];
474 // Keep running the run-loop until there is no more input to process.
475 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
476 == kCFRunLoopRunHandledSource)
480 - (void)flushQueue:(BOOL)force
482 // NOTE: This variable allows for better control over when the queue is
483 // flushed. It can be set to YES at the beginning of a sequence of calls
484 // that may potentially add items to the queue, and then restored back to
486 if (flushDisabled) return;
488 if ([drawData length] > 0) {
489 // HACK! Detect changes to 'guifontwide'.
490 if (gui.wide_font != oldWideFont) {
491 gui_mch_free_font(oldWideFont);
492 oldWideFont = gui_mch_retain_font(gui.wide_font);
493 [self setFont:oldWideFont wide:YES];
496 int type = SetCursorPosDrawType;
497 [drawData appendBytes:&type length:sizeof(type)];
498 [drawData appendBytes:&gui.row length:sizeof(gui.row)];
499 [drawData appendBytes:&gui.col length:sizeof(gui.col)];
501 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
502 [self clearDrawData];
505 if ([outputQueue count] > 0) {
506 [self insertVimStateMessage];
509 [frontendProxy processCommandQueue:outputQueue];
511 @catch (NSException *e) {
512 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
513 NSLog(@"outputQueue(len:%d)=%@", [outputQueue count]/2,
515 if (![connection isValid]) {
516 NSLog(@"WARNING! Connection is invalid, exit now!");
517 NSLog(@"waitForAck=%d got_int=%d", waitForAck, got_int);
522 [outputQueue removeAllObjects];
526 - (BOOL)waitForInput:(int)milliseconds
528 // Return NO if we timed out waiting for input, otherwise return YES.
529 BOOL inputReceived = NO;
531 // Only start the run loop if the input queue is empty, otherwise process
532 // the input first so that the input on queue isn't delayed.
533 if ([inputQueue count]) {
536 // Wait for the specified amount of time, unless 'milliseconds' is
537 // negative in which case we wait "forever" (1e6 seconds translates to
538 // approximately 11 days).
539 CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
541 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
542 == kCFRunLoopRunHandledSource) {
543 // In order to ensure that all input on the run-loop has been
544 // processed we set the timeout to 0 and keep processing until the
545 // run-loop times out.
551 // The above calls may have placed messages on the input queue so process
552 // it now. This call may enter a blocking loop.
553 if ([inputQueue count] > 0)
554 [self processInputQueue];
556 return inputReceived;
561 // NOTE: This is called if mch_exit() is called. Since we assume here that
562 // the process has started properly, be sure to use exit() instead of
563 // mch_exit() to prematurely terminate a process (or set 'isTerminating'
566 // Make sure no connectionDidDie: notification is received now that we are
568 [[NSNotificationCenter defaultCenter] removeObserver:self];
570 // The 'isTerminating' flag indicates that the frontend is also exiting so
571 // there is no need to flush any more output since the frontend won't look
573 if (!isTerminating && [connection isValid]) {
575 // Flush the entire queue in case a VimLeave autocommand added
576 // something to the queue.
577 [self queueMessage:CloseWindowMsgID data:nil];
578 [frontendProxy processCommandQueue:outputQueue];
580 @catch (NSException *e) {
581 NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
584 // NOTE: If Cmd-w was pressed to close the window the menu is briefly
585 // highlighted and during this pause the frontend won't receive any DO
586 // messages. If the Vim process exits before this highlighting has
587 // finished Cocoa will emit the following error message:
588 // *** -[NSMachPort handlePortMessage:]: dropping incoming DO message
589 // because the connection or ports are invalid
590 // To avoid this warning we delay here. If the warning still appears
591 // this delay may need to be increased.
595 #ifdef MAC_CLIENTSERVER
596 // The default connection is used for the client/server code.
597 [[NSConnection defaultConnection] setRootObject:nil];
598 [[NSConnection defaultConnection] invalidate];
602 - (void)selectTab:(int)index
604 //NSLog(@"%s%d", _cmd, index);
607 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
608 [self queueMessage:SelectTabMsgID data:data];
613 //NSLog(@"%s", _cmd);
615 NSMutableData *data = [NSMutableData data];
617 int idx = tabpage_index(curtab) - 1;
618 [data appendBytes:&idx length:sizeof(int)];
621 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
622 // Count the number of windows in the tabpage.
623 //win_T *wp = tp->tp_firstwin;
625 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
626 //[data appendBytes:&wincount length:sizeof(int)];
628 int tabProp = MMTabInfoCount;
629 [data appendBytes:&tabProp length:sizeof(int)];
630 for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
631 // This function puts the label of the tab in the global 'NameBuff'.
632 get_tabline_label(tp, (tabProp == MMTabToolTip));
633 NSString *s = [NSString stringWithVimString:NameBuff];
634 int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
638 [data appendBytes:&len length:sizeof(int)];
640 [data appendBytes:[s UTF8String] length:len];
644 [self queueMessage:UpdateTabBarMsgID data:data];
647 - (BOOL)tabBarVisible
649 return tabBarVisible;
652 - (void)showTabBar:(BOOL)enable
654 tabBarVisible = enable;
656 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
657 [self queueMessage:msgid data:nil];
660 - (void)setRows:(int)rows columns:(int)cols
662 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
664 int dim[] = { rows, cols };
665 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
667 [self queueMessage:SetTextDimensionsMsgID data:data];
670 - (void)setWindowTitle:(char *)title
672 NSMutableData *data = [NSMutableData data];
673 int len = strlen(title);
674 if (len <= 0) return;
676 [data appendBytes:&len length:sizeof(int)];
677 [data appendBytes:title length:len];
679 [self queueMessage:SetWindowTitleMsgID data:data];
682 - (void)setDocumentFilename:(char *)filename
684 NSMutableData *data = [NSMutableData data];
685 int len = filename ? strlen(filename) : 0;
687 [data appendBytes:&len length:sizeof(int)];
689 [data appendBytes:filename length:len];
691 [self queueMessage:SetDocumentFilenameMsgID data:data];
694 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
699 [frontendProxy showSavePanelWithAttributes:attr];
701 [self waitForDialogReturn];
703 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
704 s = [dialogReturn vimStringSave];
706 [dialogReturn release]; dialogReturn = nil;
708 @catch (NSException *e) {
709 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
715 - (oneway void)setDialogReturn:(in bycopy id)obj
717 // NOTE: This is called by
718 // - [MMVimController panelDidEnd:::], and
719 // - [MMVimController alertDidEnd:::],
720 // to indicate that a save/open panel or alert has finished.
722 // We want to distinguish between "no dialog return yet" and "dialog
723 // returned nothing". The former can be tested with dialogReturn == nil,
724 // the latter with dialogReturn == [NSNull null].
725 if (!obj) obj = [NSNull null];
727 if (obj != dialogReturn) {
728 [dialogReturn release];
729 dialogReturn = [obj retain];
733 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
738 [frontendProxy presentDialogWithAttributes:attr];
740 [self waitForDialogReturn];
742 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
743 && [dialogReturn count]) {
744 retval = [[dialogReturn objectAtIndex:0] intValue];
745 if (txtfield && [dialogReturn count] > 1) {
746 NSString *retString = [dialogReturn objectAtIndex:1];
747 char_u *ret = (char_u*)[retString UTF8String];
749 ret = CONVERT_FROM_UTF8(ret);
751 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
753 CONVERT_FROM_UTF8_FREE(ret);
758 [dialogReturn release]; dialogReturn = nil;
760 @catch (NSException *e) {
761 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
767 - (void)showToolbar:(int)enable flags:(int)flags
769 NSMutableData *data = [NSMutableData data];
771 [data appendBytes:&enable length:sizeof(int)];
772 [data appendBytes:&flags length:sizeof(int)];
774 [self queueMessage:ShowToolbarMsgID data:data];
777 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
779 NSMutableData *data = [NSMutableData data];
781 [data appendBytes:&ident length:sizeof(long)];
782 [data appendBytes:&type length:sizeof(int)];
784 [self queueMessage:CreateScrollbarMsgID data:data];
787 - (void)destroyScrollbarWithIdentifier:(long)ident
789 NSMutableData *data = [NSMutableData data];
790 [data appendBytes:&ident length:sizeof(long)];
792 [self queueMessage:DestroyScrollbarMsgID data:data];
795 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
797 NSMutableData *data = [NSMutableData data];
799 [data appendBytes:&ident length:sizeof(long)];
800 [data appendBytes:&visible length:sizeof(int)];
802 [self queueMessage:ShowScrollbarMsgID data:data];
805 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
807 NSMutableData *data = [NSMutableData data];
809 [data appendBytes:&ident length:sizeof(long)];
810 [data appendBytes:&pos length:sizeof(int)];
811 [data appendBytes:&len length:sizeof(int)];
813 [self queueMessage:SetScrollbarPositionMsgID data:data];
816 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
817 identifier:(long)ident
819 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
820 float prop = (float)size/(max+1);
821 if (fval < 0) fval = 0;
822 else if (fval > 1.0f) fval = 1.0f;
823 if (prop < 0) prop = 0;
824 else if (prop > 1.0f) prop = 1.0f;
826 NSMutableData *data = [NSMutableData data];
828 [data appendBytes:&ident length:sizeof(long)];
829 [data appendBytes:&fval length:sizeof(float)];
830 [data appendBytes:&prop length:sizeof(float)];
832 [self queueMessage:SetScrollbarThumbMsgID data:data];
835 - (void)setFont:(GuiFont)font wide:(BOOL)wide
837 NSString *fontName = (NSString *)font;
839 NSArray *components = [fontName componentsSeparatedByString:@":"];
840 if ([components count] == 2) {
841 size = [[components lastObject] floatValue];
842 fontName = [components objectAtIndex:0];
845 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
846 NSMutableData *data = [NSMutableData data];
847 [data appendBytes:&size length:sizeof(float)];
848 [data appendBytes:&len length:sizeof(int)];
851 [data appendBytes:[fontName UTF8String] length:len];
853 return; // Only the wide font can be set to nothing
855 [self queueMessage:(wide ? SetWideFontMsgID : SetFontMsgID) data:data];
858 - (void)executeActionWithName:(NSString *)name
860 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
863 NSMutableData *data = [NSMutableData data];
865 [data appendBytes:&len length:sizeof(int)];
866 [data appendBytes:[name UTF8String] length:len];
868 [self queueMessage:ExecuteActionMsgID data:data];
872 - (void)setMouseShape:(int)shape
874 NSMutableData *data = [NSMutableData data];
875 [data appendBytes:&shape length:sizeof(int)];
876 [self queueMessage:SetMouseShapeMsgID data:data];
879 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
881 // Vim specifies times in milliseconds, whereas Cocoa wants them in
883 blinkWaitInterval = .001f*wait;
884 blinkOnInterval = .001f*on;
885 blinkOffInterval = .001f*off;
891 [blinkTimer invalidate];
892 [blinkTimer release];
896 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
898 blinkState = MMBlinkStateOn;
900 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
902 selector:@selector(blinkTimerFired:)
903 userInfo:nil repeats:NO] retain];
904 gui_update_cursor(TRUE, FALSE);
905 [self flushQueue:YES];
911 if (MMBlinkStateOff == blinkState) {
912 gui_update_cursor(TRUE, FALSE);
913 [self flushQueue:YES];
916 blinkState = MMBlinkStateNone;
919 - (void)adjustLinespace:(int)linespace
921 NSMutableData *data = [NSMutableData data];
922 [data appendBytes:&linespace length:sizeof(int)];
923 [self queueMessage:AdjustLinespaceMsgID data:data];
928 [self queueMessage:ActivateMsgID data:nil];
931 - (void)setPreEditRow:(int)row column:(int)col
933 NSMutableData *data = [NSMutableData data];
934 [data appendBytes:&row length:sizeof(int)];
935 [data appendBytes:&col length:sizeof(int)];
936 [self queueMessage:SetPreEditPositionMsgID data:data];
939 - (int)lookupColorWithKey:(NSString *)key
941 if (!(key && [key length] > 0))
944 NSString *stripKey = [[[[key lowercaseString]
945 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
946 componentsSeparatedByString:@" "]
947 componentsJoinedByString:@""];
949 if (stripKey && [stripKey length] > 0) {
950 // First of all try to lookup key in the color dictionary; note that
951 // all keys in this dictionary are lowercase with no whitespace.
952 id obj = [colorDict objectForKey:stripKey];
953 if (obj) return [obj intValue];
955 // The key was not in the dictionary; is it perhaps of the form
957 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
958 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
959 [scanner setScanLocation:1];
961 if ([scanner scanHexInt:&hex]) {
966 // As a last resort, check if it is one of the system defined colors.
967 // The keys in this dictionary are also lowercase with no whitespace.
968 obj = [sysColorDict objectForKey:stripKey];
970 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
973 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
974 [col getRed:&r green:&g blue:&b alpha:&a];
975 return (((int)(r*255+.5f) & 0xff) << 16)
976 + (((int)(g*255+.5f) & 0xff) << 8)
977 + ((int)(b*255+.5f) & 0xff);
982 //NSLog(@"WARNING: No color with key %@ found.", stripKey);
986 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
988 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
991 while ((obj = [e nextObject])) {
992 if ([value isEqual:obj])
999 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1001 NSMutableData *data = [NSMutableData data];
1002 [data appendBytes:&fuoptions length:sizeof(int)];
1004 [data appendBytes:&bg length:sizeof(int)];
1005 [self queueMessage:EnterFullscreenMsgID data:data];
1008 - (void)leaveFullscreen
1010 [self queueMessage:LeaveFullscreenMsgID data:nil];
1013 - (void)setFullscreenBackgroundColor:(int)color
1015 NSMutableData *data = [NSMutableData data];
1016 color = MM_COLOR(color);
1017 [data appendBytes:&color length:sizeof(int)];
1019 [self queueMessage:SetFullscreenColorMsgID data:data];
1022 - (void)setAntialias:(BOOL)antialias
1024 int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1026 [self queueMessage:msgid data:nil];
1029 - (void)updateModifiedFlag
1031 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1033 int msgid = [self checkForModifiedBuffers]
1034 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1036 [self queueMessage:msgid data:nil];
1039 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1041 // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1042 // queue is processed since that only happens in waitForInput: (and Vim
1043 // regularly checks for Ctrl-C in between waiting for input).
1044 // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1045 // which waits on the run loop will fail to detect this message (e.g. in
1046 // waitForConnectionAcknowledgement).
1048 if (InsertTextMsgID == msgid && data != nil) {
1049 const void *bytes = [data bytes];
1050 bytes += sizeof(int);
1051 int len = *((int*)bytes); bytes += sizeof(int);
1053 char_u *str = (char_u*)bytes;
1054 if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1055 (str[0] == intr_char && intr_char != Ctrl_C)) {
1057 [inputQueue removeAllObjects];
1061 } else if (TerminateNowMsgID == msgid) {
1062 // Terminate immediately (the frontend is about to quit or this process
1063 // was aborted). Don't preserve modified files since the user would
1064 // already have been presented with a dialog warning if there were any
1065 // modified files when we get here.
1066 isTerminating = YES;
1071 // Remove all previous instances of this message from the input queue, else
1072 // the input queue may fill up as a result of Vim not being able to keep up
1073 // with the speed at which new messages are received.
1074 // Keyboard input is never dropped, unless the input represents and
1075 // auto-repeated key.
1077 BOOL isKeyRepeat = NO;
1078 BOOL isKeyboardInput = NO;
1080 if (data && (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1081 CmdKeyMsgID == msgid)) {
1082 isKeyboardInput = YES;
1084 // The lowest bit of the first int is set if this key is a repeat.
1085 int flags = *((int*)[data bytes]);
1090 // Keyboard input is not removed from the queue; repeats are ignored if
1091 // there already is keyboard input on the input queue.
1092 if (isKeyRepeat || !isKeyboardInput) {
1093 int i, count = [inputQueue count];
1094 for (i = 1; i < count; i+=2) {
1095 if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1099 [inputQueue removeObjectAtIndex:i];
1100 [inputQueue removeObjectAtIndex:i-1];
1106 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1107 [inputQueue addObject:(data ? (id)data : [NSNull null])];
1110 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1112 // This is just a convenience method that allows the frontend to delay
1113 // sending messages.
1114 int i, count = [messages count];
1115 for (i = 1; i < count; i+=2)
1116 [self processInput:[[messages objectAtIndex:i-1] intValue]
1117 data:[messages objectAtIndex:i]];
1120 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1121 errorString:(out bycopy NSString **)errstr
1123 return evalExprCocoa(expr, errstr);
1127 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1129 NSString *eval = nil;
1130 char_u *s = (char_u*)[expr UTF8String];
1133 s = CONVERT_FROM_UTF8(s);
1136 char_u *res = eval_client_expr_to_string(s);
1139 CONVERT_FROM_UTF8_FREE(s);
1145 s = CONVERT_TO_UTF8(s);
1147 eval = [NSString stringWithUTF8String:(char*)s];
1149 CONVERT_TO_UTF8_FREE(s);
1157 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1159 // TODO: This method should share code with clip_mch_request_selection().
1161 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1162 // If there is no pasteboard, return YES to indicate that there is text
1167 clip_copy_selection();
1169 // Get the text to put on the pasteboard.
1170 long_u llen = 0; char_u *str = 0;
1171 int type = clip_convert_selection(&str, &llen, &clip_star);
1175 // TODO: Avoid overflow.
1176 int len = (int)llen;
1178 if (output_conv.vc_type != CONV_NONE) {
1179 char_u *conv_str = string_convert(&output_conv, str, &len);
1187 NSString *string = [[NSString alloc]
1188 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1190 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1191 [pboard declareTypes:types owner:nil];
1192 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1203 - (oneway void)addReply:(in bycopy NSString *)reply
1204 server:(in byref id <MMVimServerProtocol>)server
1206 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1208 // Replies might come at any time and in any order so we keep them in an
1209 // array inside a dictionary with the send port used as key.
1211 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1212 // HACK! Assume connection uses mach ports.
1213 int port = [(NSMachPort*)[conn sendPort] machPort];
1214 NSNumber *key = [NSNumber numberWithInt:port];
1216 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1218 replies = [NSMutableArray array];
1219 [serverReplyDict setObject:replies forKey:key];
1222 [replies addObject:reply];
1225 - (void)addInput:(in bycopy NSString *)input
1226 client:(in byref id <MMVimClientProtocol>)client
1228 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1230 // NOTE: We don't call addInput: here because it differs from
1231 // server_to_input_buf() in that it always sets the 'silent' flag and we
1232 // don't want the MacVim client/server code to behave differently from
1234 char_u *s = [input vimStringSave];
1235 server_to_input_buf(s);
1238 [self addClient:(id)client];
1241 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1242 client:(in byref id <MMVimClientProtocol>)client
1244 [self addClient:(id)client];
1245 return [self evaluateExpression:expr];
1248 - (void)registerServerWithName:(NSString *)name
1250 NSString *svrName = name;
1251 NSConnection *svrConn = [NSConnection defaultConnection];
1254 for (i = 0; i < MMServerMax; ++i) {
1255 NSString *connName = [self connectionNameFromServerName:svrName];
1257 if ([svrConn registerName:connName]) {
1258 //NSLog(@"Registered server with name: %@", svrName);
1260 // TODO: Set request/reply time-outs to something else?
1262 // Don't wait for requests (time-out means that the message is
1264 [svrConn setRequestTimeout:0];
1265 //[svrConn setReplyTimeout:MMReplyTimeout];
1266 [svrConn setRootObject:self];
1268 // NOTE: 'serverName' is a global variable
1269 serverName = [svrName vimStringSave];
1271 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1274 need_maketitle = TRUE;
1276 [self queueMessage:SetServerNameMsgID data:
1277 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1281 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1285 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1286 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1289 // NOTE: If 'name' equals 'serverName' then the request is local (client
1290 // and server are the same). This case is not handled separately, so a
1291 // connection will be set up anyway (this simplifies the code).
1293 NSConnection *conn = [self connectionForServerName:name];
1296 char_u *s = (char_u*)[name UTF8String];
1298 s = CONVERT_FROM_UTF8(s);
1300 EMSG2(_(e_noserver), s);
1302 CONVERT_FROM_UTF8_FREE(s);
1309 // HACK! Assume connection uses mach ports.
1310 *port = [(NSMachPort*)[conn sendPort] machPort];
1313 id proxy = [conn rootProxy];
1314 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1318 NSString *eval = [proxy evaluateExpression:string client:self];
1321 *reply = [eval vimStringSave];
1323 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1330 [proxy addInput:string client:self];
1333 @catch (NSException *e) {
1334 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1341 - (NSArray *)serverList
1343 NSArray *list = nil;
1345 if ([self connection]) {
1346 id proxy = [connection rootProxy];
1347 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1350 list = [proxy serverList];
1352 @catch (NSException *e) {
1353 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1356 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1362 - (NSString *)peekForReplyOnPort:(int)port
1364 //NSLog(@"%s%d", _cmd, port);
1366 NSNumber *key = [NSNumber numberWithInt:port];
1367 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1368 if (replies && [replies count]) {
1369 //NSLog(@" %d replies, topmost is: %@", [replies count],
1370 // [replies objectAtIndex:0]);
1371 return [replies objectAtIndex:0];
1374 //NSLog(@" No replies");
1378 - (NSString *)waitForReplyOnPort:(int)port
1380 //NSLog(@"%s%d", _cmd, port);
1382 NSConnection *conn = [self connectionForServerPort:port];
1386 NSNumber *key = [NSNumber numberWithInt:port];
1387 NSMutableArray *replies = nil;
1388 NSString *reply = nil;
1390 // Wait for reply as long as the connection to the server is valid (unless
1391 // user interrupts wait with Ctrl-C).
1392 while (!got_int && [conn isValid] &&
1393 !(replies = [serverReplyDict objectForKey:key])) {
1394 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1395 beforeDate:[NSDate distantFuture]];
1399 if ([replies count] > 0) {
1400 reply = [[replies objectAtIndex:0] retain];
1401 //NSLog(@" Got reply: %@", reply);
1402 [replies removeObjectAtIndex:0];
1403 [reply autorelease];
1406 if ([replies count] == 0)
1407 [serverReplyDict removeObjectForKey:key];
1413 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1415 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1418 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1419 [client addReply:reply server:self];
1422 @catch (NSException *e) {
1423 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1426 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1437 - (void)setWaitForAck:(BOOL)yn
1442 - (void)waitForConnectionAcknowledgement
1444 if (!waitForAck) return;
1446 while (waitForAck && !got_int && [connection isValid]) {
1447 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1448 beforeDate:[NSDate distantFuture]];
1449 //NSLog(@" waitForAck=%d got_int=%d isValid=%d",
1450 // waitForAck, got_int, [connection isValid]);
1454 // Never received a connection acknowledgement, so die.
1455 [[NSNotificationCenter defaultCenter] removeObserver:self];
1456 [frontendProxy release]; frontendProxy = nil;
1458 // NOTE: We intentionally do not call mch_exit() since this in turn
1459 // will lead to -[MMBackend exit] getting called which we want to
1464 [self processInputQueue];
1467 - (oneway void)acknowledgeConnection
1469 //NSLog(@"%s", _cmd);
1477 @implementation MMBackend (Private)
1479 - (void)clearDrawData
1481 [drawData setLength:0];
1482 numWholeLineChanges = offsetForDrawDataPrune = 0;
1485 - (void)didChangeWholeLine
1487 // It may happen that draw queue is filled up with lots of changes that
1488 // affect a whole row. If the number of such changes equals twice the
1489 // number of visible rows then we can prune some commands off the queue.
1491 // NOTE: If we don't perform this pruning the draw queue may grow
1492 // indefinitely if Vim were to repeatedly send draw commands without ever
1493 // waiting for new input (that's when the draw queue is flushed). The one
1494 // instance I know where this can happen is when a command is executed in
1495 // the shell (think ":grep" with thousands of matches).
1497 ++numWholeLineChanges;
1498 if (numWholeLineChanges == gui.num_rows) {
1499 // Remember the offset to prune up to.
1500 offsetForDrawDataPrune = [drawData length];
1501 } else if (numWholeLineChanges == 2*gui.num_rows) {
1502 // Delete all the unnecessary draw commands.
1503 NSMutableData *d = [[NSMutableData alloc]
1504 initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1505 length:[drawData length] - offsetForDrawDataPrune];
1506 offsetForDrawDataPrune = [d length];
1507 numWholeLineChanges -= gui.num_rows;
1513 - (void)waitForDialogReturn
1515 // Keep processing the run loop until a dialog returns. To avoid getting
1516 // stuck in an endless loop (could happen if the setDialogReturn: message
1517 // was lost) we also do some paranoia checks.
1519 // Note that in Cocoa the user can still resize windows and select menu
1520 // items while a sheet is being displayed, so we can't just wait for the
1521 // first message to arrive and assume that is the setDialogReturn: call.
1523 while (nil == dialogReturn && !got_int && [connection isValid])
1524 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1525 beforeDate:[NSDate distantFuture]];
1527 // Search for any resize messages on the input queue. All other messages
1528 // on the input queue are dropped. The reason why we single out resize
1529 // messages is because the user may have resized the window while a sheet
1531 int i, count = [inputQueue count];
1533 id textDimData = nil;
1535 for (i = count-2; i >= 0; i -= 2) {
1536 int msgid = [[inputQueue objectAtIndex:i] intValue];
1537 if (SetTextDimensionsMsgID == msgid) {
1538 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1544 [inputQueue removeAllObjects];
1547 [inputQueue addObject:
1548 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1549 [inputQueue addObject:textDimData];
1550 [textDimData release];
1555 - (void)insertVimStateMessage
1557 // NOTE: This is the place to add Vim state that needs to be accessed from
1558 // MacVim. Do not add state that could potentially require lots of memory
1559 // since this message gets sent each time the output queue is forcibly
1560 // flushed (e.g. storing the currently selected text would be a bad idea).
1561 // We take this approach of "pushing" the state to MacVim to avoid having
1562 // to make synchronous calls from MacVim to Vim in order to get state.
1564 BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1565 int numTabs = tabpage_index(NULL) - 1;
1569 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1570 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1571 [NSNumber numberWithInt:p_mh], @"p_mh",
1572 [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1573 [NSNumber numberWithBool:mmta], @"p_mmta",
1574 [NSNumber numberWithInt:numTabs], @"numTabs",
1577 // Put the state before all other messages.
1578 int msgid = SetVimStateMsgID;
1579 [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1580 [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1584 - (void)processInputQueue
1586 if ([inputQueue count] == 0) return;
1588 // NOTE: One of the input events may cause this method to be called
1589 // recursively, so copy the input queue to a local variable and clear the
1590 // queue before starting to process input events (otherwise we could get
1591 // stuck in an endless loop).
1592 NSArray *q = [inputQueue copy];
1593 unsigned i, count = [q count];
1595 [inputQueue removeAllObjects];
1597 for (i = 1; i < count; i+=2) {
1598 int msgid = [[q objectAtIndex:i-1] intValue];
1599 id data = [q objectAtIndex:i];
1600 if ([data isEqual:[NSNull null]])
1603 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1604 [self handleInputEvent:msgid data:data];
1608 //NSLog(@"Clear input event queue");
1611 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1613 if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1614 CmdKeyMsgID == msgid) {
1616 const void *bytes = [data bytes];
1617 int mods = *((int*)bytes); bytes += sizeof(int);
1618 int len = *((int*)bytes); bytes += sizeof(int);
1619 NSString *key = [[NSString alloc] initWithBytes:bytes
1621 encoding:NSUTF8StringEncoding];
1622 mods = eventModifierFlagsToVimModMask(mods);
1624 if (InsertTextMsgID == msgid)
1625 [self handleInsertText:key];
1627 [self handleKeyDown:key modifiers:mods];
1630 } else if (ScrollWheelMsgID == msgid) {
1632 const void *bytes = [data bytes];
1634 int row = *((int*)bytes); bytes += sizeof(int);
1635 int col = *((int*)bytes); bytes += sizeof(int);
1636 int flags = *((int*)bytes); bytes += sizeof(int);
1637 float dy = *((float*)bytes); bytes += sizeof(float);
1639 int button = MOUSE_5;
1640 if (dy > 0) button = MOUSE_4;
1642 flags = eventModifierFlagsToVimMouseModMask(flags);
1644 int numLines = (int)round(dy);
1645 if (numLines < 0) numLines = -numLines;
1646 if (numLines == 0) numLines = 1;
1648 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1649 gui.scroll_wheel_force = numLines;
1652 gui_send_mouse_event(button, col, row, NO, flags);
1653 } else if (MouseDownMsgID == msgid) {
1655 const void *bytes = [data bytes];
1657 int row = *((int*)bytes); bytes += sizeof(int);
1658 int col = *((int*)bytes); bytes += sizeof(int);
1659 int button = *((int*)bytes); bytes += sizeof(int);
1660 int flags = *((int*)bytes); bytes += sizeof(int);
1661 int count = *((int*)bytes); bytes += sizeof(int);
1663 button = eventButtonNumberToVimMouseButton(button);
1665 flags = eventModifierFlagsToVimMouseModMask(flags);
1666 gui_send_mouse_event(button, col, row, count>1, flags);
1668 } else if (MouseUpMsgID == msgid) {
1670 const void *bytes = [data bytes];
1672 int row = *((int*)bytes); bytes += sizeof(int);
1673 int col = *((int*)bytes); bytes += sizeof(int);
1674 int flags = *((int*)bytes); bytes += sizeof(int);
1676 flags = eventModifierFlagsToVimMouseModMask(flags);
1678 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1679 } else if (MouseDraggedMsgID == msgid) {
1681 const void *bytes = [data bytes];
1683 int row = *((int*)bytes); bytes += sizeof(int);
1684 int col = *((int*)bytes); bytes += sizeof(int);
1685 int flags = *((int*)bytes); bytes += sizeof(int);
1687 flags = eventModifierFlagsToVimMouseModMask(flags);
1689 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1690 } else if (MouseMovedMsgID == msgid) {
1691 const void *bytes = [data bytes];
1692 int row = *((int*)bytes); bytes += sizeof(int);
1693 int col = *((int*)bytes); bytes += sizeof(int);
1695 gui_mouse_moved(col, row);
1696 } else if (AddInputMsgID == msgid) {
1697 NSString *string = [[NSString alloc] initWithData:data
1698 encoding:NSUTF8StringEncoding];
1700 [self addInput:string];
1703 } else if (SelectTabMsgID == msgid) {
1705 const void *bytes = [data bytes];
1706 int idx = *((int*)bytes) + 1;
1707 //NSLog(@"Selecting tab %d", idx);
1708 send_tabline_event(idx);
1709 } else if (CloseTabMsgID == msgid) {
1711 const void *bytes = [data bytes];
1712 int idx = *((int*)bytes) + 1;
1713 //NSLog(@"Closing tab %d", idx);
1714 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1715 } else if (AddNewTabMsgID == msgid) {
1716 //NSLog(@"Adding new tab");
1717 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1718 } else if (DraggedTabMsgID == msgid) {
1720 const void *bytes = [data bytes];
1721 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1723 int idx = *((int*)bytes);
1726 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1727 || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1729 const void *bytes = [data bytes];
1731 if (SetTextColumnsMsgID != msgid) {
1732 rows = *((int*)bytes); bytes += sizeof(int);
1735 if (SetTextRowsMsgID != msgid) {
1736 cols = *((int*)bytes); bytes += sizeof(int);
1740 if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1741 int dim[2] = { rows, cols };
1742 d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1743 msgid = SetTextDimensionsReplyMsgID;
1746 if (SetTextDimensionsMsgID == msgid)
1747 msgid = SetTextDimensionsReplyMsgID;
1749 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1750 // gui_resize_shell(), so we have to manually set the rows and columns
1751 // here since MacVim doesn't change the rows and columns to avoid
1752 // inconsistent states between Vim and MacVim. The message sent back
1753 // indicates that it is a reply to a message that originated in MacVim
1754 // since we need to be able to determine where a message originated.
1755 [self queueMessage:msgid data:d];
1757 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1758 gui_resize_shell(cols, rows);
1759 } else if (ExecuteMenuMsgID == msgid) {
1760 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1762 NSArray *desc = [attrs objectForKey:@"descriptor"];
1763 vimmenu_T *menu = menu_for_descriptor(desc);
1767 } else if (ToggleToolbarMsgID == msgid) {
1768 [self handleToggleToolbar];
1769 } else if (ScrollbarEventMsgID == msgid) {
1770 [self handleScrollbarEvent:data];
1771 } else if (SetFontMsgID == msgid) {
1772 [self handleSetFont:data];
1773 } else if (VimShouldCloseMsgID == msgid) {
1775 } else if (DropFilesMsgID == msgid) {
1776 [self handleDropFiles:data];
1777 } else if (DropStringMsgID == msgid) {
1778 [self handleDropString:data];
1779 } else if (GotFocusMsgID == msgid) {
1781 [self focusChange:YES];
1782 } else if (LostFocusMsgID == msgid) {
1784 [self focusChange:NO];
1785 } else if (SetMouseShapeMsgID == msgid) {
1786 const void *bytes = [data bytes];
1787 int shape = *((int*)bytes); bytes += sizeof(int);
1788 update_mouseshape(shape);
1789 } else if (XcodeModMsgID == msgid) {
1790 [self handleXcodeMod:data];
1791 } else if (OpenWithArgumentsMsgID == msgid) {
1792 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1793 } else if (FindReplaceMsgID == msgid) {
1794 [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1796 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1800 + (NSDictionary *)specialKeys
1802 static NSDictionary *specialKeys = nil;
1805 NSBundle *mainBundle = [NSBundle mainBundle];
1806 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1808 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1814 - (void)handleInsertText:(NSString *)text
1818 char_u *str = (char_u*)[text UTF8String];
1819 int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1822 char_u *conv_str = NULL;
1823 if (input_conv.vc_type != CONV_NONE) {
1824 conv_str = string_convert(&input_conv, str, &len);
1830 for (i = 0; i < len; ++i) {
1831 add_to_input_buf(str+i, 1);
1832 if (CSI == str[i]) {
1833 // NOTE: If the converted string contains the byte CSI, then it
1834 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1836 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1837 add_to_input_buf(extra, 2);
1847 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1849 // TODO: This code is a horrible mess -- clean up!
1852 char_u *chars = (char_u*)[key UTF8String];
1854 char_u *conv_str = NULL;
1856 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1858 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1859 // that new keys can easily be added.
1860 NSString *specialString = [[MMBackend specialKeys]
1862 if (specialString && [specialString length] > 1) {
1863 //NSLog(@"special key: %@", specialString);
1864 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1865 [specialString characterAtIndex:1]);
1867 ikey = simplify_key(ikey, &mods);
1872 special[1] = K_SECOND(ikey);
1873 special[2] = K_THIRD(ikey);
1877 } else if (1 == length && TAB == chars[0]) {
1878 // Tab is a trouble child:
1879 // - <Tab> is added to the input buffer as is
1880 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1881 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1882 // to be converted to utf-8
1883 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1884 // - <C-Tab> is reserved by Mac OS X
1885 // - <D-Tab> is reserved by Mac OS X
1890 if (mods & MOD_MASK_SHIFT) {
1891 mods &= ~MOD_MASK_SHIFT;
1893 special[1] = K_SECOND(K_S_TAB);
1894 special[2] = K_THIRD(K_S_TAB);
1896 } else if (mods & MOD_MASK_ALT) {
1897 int mtab = 0x80 | TAB;
1901 special[0] = (mtab >> 6) + 0xc0;
1902 special[1] = mtab & 0xbf;
1910 mods &= ~MOD_MASK_ALT;
1912 } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1913 // META key is treated separately. This code was taken from gui_w48.c
1914 // and gui_gtk_x11.c.
1916 int ch = simplify_key(chars[0], &mods);
1918 // Remove the SHIFT modifier for keys where it's already included,
1919 // e.g., '(' and '*'
1920 if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1921 mods &= ~MOD_MASK_SHIFT;
1923 // Interpret the ALT key as making the key META, include SHIFT, etc.
1924 ch = extract_modifiers(ch, &mods);
1930 string[len++] = CSI;
1931 string[len++] = KS_MODIFIER;
1932 string[len++] = mods;
1935 if (IS_SPECIAL(ch)) {
1936 string[len++] = CSI;
1937 string[len++] = K_SECOND(ch);
1938 string[len++] = K_THIRD(ch);
1942 // TODO: What if 'enc' is not "utf-8"?
1943 if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1944 string[len++] = ch & 0xbf;
1945 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1946 if (string[len-1] == CSI) {
1947 string[len++] = KS_EXTRA;
1948 string[len++] = (int)KE_CSI;
1954 add_to_input_buf(string, len);
1956 } else if (length > 0) {
1957 unichar c = [key characterAtIndex:0];
1958 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1959 // [key characterAtIndex:0], mods);
1961 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1962 // cleared since they are already added to the key by the AppKit.
1963 // Unfortunately, the only way to deal with when to clear the modifiers
1964 // or not seems to be to have hard-wired rules like this.
1965 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1966 || 0x9 == c || 0xd == c || ESC == c) ) {
1967 mods &= ~MOD_MASK_SHIFT;
1968 mods &= ~MOD_MASK_CTRL;
1969 //NSLog(@"clear shift ctrl");
1973 if (input_conv.vc_type != CONV_NONE) {
1974 conv_str = string_convert(&input_conv, chars, &length);
1981 if (chars && length > 0) {
1983 //NSLog(@"adding mods: %d", mods);
1985 modChars[1] = KS_MODIFIER;
1987 add_to_input_buf(modChars, 3);
1990 //NSLog(@"add to input buf: 0x%x", chars[0]);
1991 // TODO: Check for CSI bytes?
1992 add_to_input_buf(chars, length);
2001 - (void)queueMessage:(int)msgid data:(NSData *)data
2003 //if (msgid != EnableMenuItemMsgID)
2004 // NSLog(@"queueMessage:%s", MessageStrings[msgid]);
2006 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2008 [outputQueue addObject:data];
2010 [outputQueue addObject:[NSData data]];
2013 - (void)connectionDidDie:(NSNotification *)notification
2015 // If the main connection to MacVim is lost this means that either MacVim
2016 // has crashed or this process did not receive its termination message
2017 // properly (e.g. if the TerminateNowMsgID was dropped).
2019 // NOTE: This is not called if a Vim controller invalidates its connection.
2021 NSLog(@"WARNING[%s]: Main connection was lost before process had a chance "
2022 "to terminate; preserving swap files.", _cmd);
2023 getout_preserve_modified(1);
2026 - (void)blinkTimerFired:(NSTimer *)timer
2028 NSTimeInterval timeInterval = 0;
2030 [blinkTimer release];
2033 if (MMBlinkStateOn == blinkState) {
2034 gui_undraw_cursor();
2035 blinkState = MMBlinkStateOff;
2036 timeInterval = blinkOffInterval;
2037 } else if (MMBlinkStateOff == blinkState) {
2038 gui_update_cursor(TRUE, FALSE);
2039 blinkState = MMBlinkStateOn;
2040 timeInterval = blinkOnInterval;
2043 if (timeInterval > 0) {
2045 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2046 selector:@selector(blinkTimerFired:)
2047 userInfo:nil repeats:NO] retain];
2048 [self flushQueue:YES];
2052 - (void)focusChange:(BOOL)on
2054 gui_focus_change(on);
2057 - (void)handleToggleToolbar
2059 // If 'go' contains 'T', then remove it, else add it.
2061 char_u go[sizeof(GO_ALL)+2];
2066 p = vim_strchr(go, GO_TOOLBAR);
2070 char_u *end = go + len;
2076 go[len] = GO_TOOLBAR;
2080 set_option_value((char_u*)"guioptions", 0, go, 0);
2082 [self redrawScreen];
2085 - (void)handleScrollbarEvent:(NSData *)data
2089 const void *bytes = [data bytes];
2090 long ident = *((long*)bytes); bytes += sizeof(long);
2091 int hitPart = *((int*)bytes); bytes += sizeof(int);
2092 float fval = *((float*)bytes); bytes += sizeof(float);
2093 scrollbar_T *sb = gui_find_scrollbar(ident);
2096 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2097 long value = sb_info->value;
2098 long size = sb_info->size;
2099 long max = sb_info->max;
2100 BOOL isStillDragging = NO;
2101 BOOL updateKnob = YES;
2104 case NSScrollerDecrementPage:
2105 value -= (size > 2 ? size - 2 : 1);
2107 case NSScrollerIncrementPage:
2108 value += (size > 2 ? size - 2 : 1);
2110 case NSScrollerDecrementLine:
2113 case NSScrollerIncrementLine:
2116 case NSScrollerKnob:
2117 isStillDragging = YES;
2119 case NSScrollerKnobSlot:
2120 value = (long)(fval * (max - size + 1));
2127 //NSLog(@"value %d -> %d", sb_info->value, value);
2128 gui_drag_scrollbar(sb, value, isStillDragging);
2131 // Dragging the knob or option+clicking automatically updates
2132 // the knob position (on the actual NSScroller), so we only
2133 // need to set the knob position in the other cases.
2135 // Update both the left&right vertical scrollbars.
2136 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2137 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2138 [self setScrollbarThumbValue:value size:size max:max
2139 identifier:identLeft];
2140 [self setScrollbarThumbValue:value size:size max:max
2141 identifier:identRight];
2143 // Update the horizontal scrollbar.
2144 [self setScrollbarThumbValue:value size:size max:max
2151 - (void)handleSetFont:(NSData *)data
2155 const void *bytes = [data bytes];
2156 float pointSize = *((float*)bytes); bytes += sizeof(float);
2157 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2158 bytes += sizeof(unsigned); // len not used
2160 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2161 [name appendString:[NSString stringWithFormat:@":h%d", (int)pointSize]];
2162 char_u *s = (char_u*)[name UTF8String];
2165 s = CONVERT_FROM_UTF8(s);
2168 set_option_value((char_u*)"guifont", 0, s, 0);
2171 CONVERT_FROM_UTF8_FREE(s);
2174 [self redrawScreen];
2177 - (void)handleDropFiles:(NSData *)data
2179 // TODO: Get rid of this method; instead use Vim script directly. At the
2180 // moment I know how to do this to open files in tabs, but I'm not sure how
2181 // to add the filenames to the command line when in command line mode.
2185 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2188 id obj = [args objectForKey:@"forceOpen"];
2189 BOOL forceOpen = YES;
2191 forceOpen = [obj boolValue];
2193 NSArray *filenames = [args objectForKey:@"filenames"];
2194 if (!(filenames && [filenames count] > 0)) return;
2197 if (!forceOpen && (State & CMDLINE)) {
2198 // HACK! If Vim is in command line mode then the files names
2199 // should be added to the command line, instead of opening the
2200 // files in tabs (unless forceOpen is set). This is taken care of by
2201 // gui_handle_drop().
2202 int n = [filenames count];
2203 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2206 for (i = 0; i < n; ++i)
2207 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2209 // NOTE! This function will free 'fnames'.
2210 // HACK! It is assumed that the 'x' and 'y' arguments are
2211 // unused when in command line mode.
2212 gui_handle_drop(0, 0, 0, fnames, n);
2217 [self handleOpenWithArguments:args];
2221 - (void)handleDropString:(NSData *)data
2226 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2227 const void *bytes = [data bytes];
2228 int len = *((int*)bytes); bytes += sizeof(int);
2229 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2231 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2232 NSRange range = { 0, [string length] };
2233 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2234 withString:@"\x0a" options:0
2237 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2238 options:0 range:range];
2241 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2242 char_u *s = (char_u*)[string UTF8String];
2244 if (input_conv.vc_type != CONV_NONE)
2245 s = string_convert(&input_conv, s, &len);
2247 dnd_yank_drag_data(s, len);
2249 if (input_conv.vc_type != CONV_NONE)
2252 add_to_input_buf(dropkey, sizeof(dropkey));
2256 - (void)startOdbEditWithArguments:(NSDictionary *)args
2258 #ifdef FEAT_ODB_EDITOR
2259 id obj = [args objectForKey:@"remoteID"];
2262 OSType serverID = [obj unsignedIntValue];
2263 NSString *remotePath = [args objectForKey:@"remotePath"];
2265 NSAppleEventDescriptor *token = nil;
2266 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2267 obj = [args objectForKey:@"remoteTokenDescType"];
2268 if (tokenData && obj) {
2269 DescType tokenType = [obj unsignedLongValue];
2270 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2274 NSArray *filenames = [args objectForKey:@"filenames"];
2275 unsigned i, numFiles = [filenames count];
2276 for (i = 0; i < numFiles; ++i) {
2277 NSString *filename = [filenames objectAtIndex:i];
2278 char_u *s = [filename vimStringSave];
2279 buf_T *buf = buflist_findname(s);
2283 if (buf->b_odb_token) {
2284 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2285 buf->b_odb_token = NULL;
2288 if (buf->b_odb_fname) {
2289 vim_free(buf->b_odb_fname);
2290 buf->b_odb_fname = NULL;
2293 buf->b_odb_server_id = serverID;
2296 buf->b_odb_token = [token retain];
2298 buf->b_odb_fname = [remotePath vimStringSave];
2300 NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2304 #endif // FEAT_ODB_EDITOR
2307 - (void)handleXcodeMod:(NSData *)data
2310 const void *bytes = [data bytes];
2311 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2312 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2316 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2317 descriptorWithDescriptorType:type
2323 - (void)handleOpenWithArguments:(NSDictionary *)args
2325 // ARGUMENT: DESCRIPTION:
2326 // -------------------------------------------------------------
2327 // filenames list of filenames
2328 // dontOpen don't open files specified in above argument
2329 // layout which layout to use to open files
2330 // selectionRange range of lines to select
2331 // searchText string to search for
2332 // cursorLine line to position the cursor on
2333 // cursorColumn column to position the cursor on
2334 // (only valid when "cursorLine" is set)
2335 // remoteID ODB parameter
2336 // remotePath ODB parameter
2337 // remoteTokenDescType ODB parameter
2338 // remoteTokenData ODB parameter
2340 //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2342 NSArray *filenames = [args objectForKey:@"filenames"];
2343 int i, numFiles = filenames ? [filenames count] : 0;
2344 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2345 int layout = [[args objectForKey:@"layout"] intValue];
2347 // Change to directory of first file to open if this is an "unused" editor
2348 // (but do not do this if editing remotely).
2349 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2350 && (starting || [self unusedEditor]) ) {
2351 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2357 // When Vim is starting we simply add the files to be opened to the
2358 // global arglist and Vim will take care of opening them for us.
2359 if (openFiles && numFiles > 0) {
2360 for (i = 0; i < numFiles; i++) {
2361 NSString *fname = [filenames objectAtIndex:i];
2364 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2365 || (p = [fname vimStringSave]) == NULL)
2366 exit(2); // See comment in -[MMBackend exit]
2368 alist_add(&global_alist, p, 2);
2371 // Vim will take care of arranging the files added to the arglist
2372 // in windows or tabs; all we must do is to specify which layout to
2374 initialWindowLayout = layout;
2377 // When Vim is already open we resort to some trickery to open the
2378 // files with the specified layout.
2380 // TODO: Figure out a better way to handle this?
2381 if (openFiles && numFiles > 0) {
2382 BOOL oneWindowInTab = topframe ? YES
2383 : (topframe->fr_layout == FR_LEAF);
2384 BOOL bufChanged = NO;
2385 BOOL bufHasFilename = NO;
2387 bufChanged = curbufIsChanged();
2388 bufHasFilename = curbuf->b_ffname != NULL;
2391 // Temporarily disable flushing since the following code may
2392 // potentially cause multiple redraws.
2393 flushDisabled = YES;
2395 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2396 if (WIN_TABS == layout && !onlyOneTab) {
2397 // By going to the last tabpage we ensure that the new tabs
2398 // will appear last (if this call is left out, the taborder
2403 // Make sure we're in normal mode first.
2404 [self addInput:@"<C-\\><C-N>"];
2407 // With "split layout" we open a new tab before opening
2408 // multiple files if the current tab has more than one window
2409 // or if there is exactly one window but whose buffer has a
2410 // filename. (The :drop command ensures modified buffers get
2411 // their own window.)
2412 if ((WIN_HOR == layout || WIN_VER == layout) &&
2413 (!oneWindowInTab || bufHasFilename))
2414 [self addInput:@":tabnew<CR>"];
2416 // The files are opened by constructing a ":drop ..." command
2417 // and executing it.
2418 NSMutableString *cmd = (WIN_TABS == layout)
2419 ? [NSMutableString stringWithString:@":tab drop"]
2420 : [NSMutableString stringWithString:@":drop"];
2422 for (i = 0; i < numFiles; ++i) {
2423 NSString *file = [filenames objectAtIndex:i];
2424 file = [file stringByEscapingSpecialFilenameCharacters];
2425 [cmd appendString:@" "];
2426 [cmd appendString:file];
2429 // Temporarily clear 'suffixes' so that the files are opened in
2430 // the same order as they appear in the "filenames" array.
2431 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2433 [self addInput:cmd];
2435 // Split the view into multiple windows if requested.
2436 if (WIN_HOR == layout)
2437 [self addInput:@"|sall"];
2438 else if (WIN_VER == layout)
2439 [self addInput:@"|vert sall"];
2441 // Restore the old value of 'suffixes'.
2442 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2444 // When opening one file we try to reuse the current window,
2445 // but not if its buffer is modified or has a filename.
2446 // However, the 'arglist' layout always opens the file in the
2448 NSString *file = [[filenames lastObject]
2449 stringByEscapingSpecialFilenameCharacters];
2451 if (WIN_HOR == layout) {
2452 if (!(bufHasFilename || bufChanged))
2453 cmd = [NSString stringWithFormat:@":e %@", file];
2455 cmd = [NSString stringWithFormat:@":sp %@", file];
2456 } else if (WIN_VER == layout) {
2457 if (!(bufHasFilename || bufChanged))
2458 cmd = [NSString stringWithFormat:@":e %@", file];
2460 cmd = [NSString stringWithFormat:@":vsp %@", file];
2461 } else if (WIN_TABS == layout) {
2462 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2463 cmd = [NSString stringWithFormat:@":e %@", file];
2465 cmd = [NSString stringWithFormat:@":tabe %@", file];
2467 // (The :drop command will split if there is a modified
2469 cmd = [NSString stringWithFormat:@":drop %@", file];
2472 [self addInput:cmd];
2473 [self addInput:@"<CR>"];
2476 // Force screen redraw (does it have to be this complicated?).
2477 // (This code was taken from the end of gui_handle_drop().)
2478 update_screen(NOT_VALID);
2481 gui_update_cursor(FALSE, FALSE);
2488 if ([args objectForKey:@"remoteID"]) {
2489 // NOTE: We have to delay processing any ODB related arguments since
2490 // the file(s) may not be opened until the input buffer is processed.
2491 [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2496 NSString *lineString = [args objectForKey:@"cursorLine"];
2497 if (lineString && [lineString intValue] > 0) {
2498 NSString *columnString = [args objectForKey:@"cursorColumn"];
2499 if (!(columnString && [columnString intValue] > 0))
2500 columnString = @"1";
2502 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2503 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2504 [self addInput:cmd];
2507 NSString *rangeString = [args objectForKey:@"selectionRange"];
2509 // Build a command line string that will select the given range of
2510 // lines. If range.length == 0, then position the cursor on the given
2511 // line but do not select.
2512 NSRange range = NSRangeFromString(rangeString);
2514 if (range.length > 0) {
2515 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2516 NSMaxRange(range), range.location];
2518 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2522 [self addInput:cmd];
2525 NSString *searchText = [args objectForKey:@"searchText"];
2527 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2532 - (BOOL)checkForModifiedBuffers
2535 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2536 if (bufIsChanged(buf)) {
2544 - (void)addInput:(NSString *)input
2546 // NOTE: This code is essentially identical to server_to_input_buf(),
2547 // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2548 char_u *string = [input vimStringSave];
2549 if (!string) return;
2551 /* Set 'cpoptions' the way we want it.
2552 * B set - backslashes are *not* treated specially
2553 * k set - keycodes are *not* reverse-engineered
2554 * < unset - <Key> sequences *are* interpreted
2555 * The last but one parameter of replace_termcodes() is TRUE so that the
2556 * <lt> sequence is recognised - needed for a real backslash.
2559 char_u *cpo_save = p_cpo;
2560 p_cpo = (char_u *)"Bk";
2561 char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2564 if (*ptr != NUL) /* trailing CTRL-V results in nothing */
2567 * Add the string to the input stream.
2568 * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2570 * First clear typed characters from the typeahead buffer, there could
2571 * be half a mapping there. Then append to the existing string, so
2572 * that multiple commands from a client are concatenated.
2574 if (typebuf.tb_maplen < typebuf.tb_len)
2575 del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2576 (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2578 /* Let input_available() know we inserted text in the typeahead
2580 typebuf_was_filled = TRUE;
2586 - (BOOL)unusedEditor
2588 BOOL oneWindowInTab = topframe ? YES
2589 : (topframe->fr_layout == FR_LEAF);
2590 BOOL bufChanged = NO;
2591 BOOL bufHasFilename = NO;
2593 bufChanged = curbufIsChanged();
2594 bufHasFilename = curbuf->b_ffname != NULL;
2597 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2599 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2602 - (void)redrawScreen
2604 // Force screen redraw (does it have to be this complicated?).
2605 redraw_all_later(CLEAR);
2606 update_screen(NOT_VALID);
2609 gui_update_cursor(FALSE, FALSE);
2611 // HACK! The cursor is not put back at the command line by the above
2612 // "redraw commands". The following test seems to do the trick though.
2613 if (State & CMDLINE)
2617 - (void)handleFindReplace:(NSDictionary *)args
2621 NSString *findString = [args objectForKey:@"find"];
2622 if (!findString) return;
2624 char_u *find = [findString vimStringSave];
2625 char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2626 int flags = [[args objectForKey:@"flags"] intValue];
2628 // NOTE: The flag 0x100 is used to indicate a backward search.
2629 gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2635 @end // MMBackend (Private)
2640 @implementation MMBackend (ClientServer)
2642 - (NSString *)connectionNameFromServerName:(NSString *)name
2644 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2646 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2650 - (NSConnection *)connectionForServerName:(NSString *)name
2652 // TODO: Try 'name%d' if 'name' fails.
2653 NSString *connName = [self connectionNameFromServerName:name];
2654 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2657 svrConn = [NSConnection connectionWithRegisteredName:connName
2659 // Try alternate server...
2660 if (!svrConn && alternateServerName) {
2661 //NSLog(@" trying to connect to alternate server: %@",
2662 // alternateServerName);
2663 connName = [self connectionNameFromServerName:alternateServerName];
2664 svrConn = [NSConnection connectionWithRegisteredName:connName
2668 // Try looking for alternate servers...
2670 //NSLog(@" looking for alternate servers...");
2671 NSString *alt = [self alternateServerNameForName:name];
2672 if (alt != alternateServerName) {
2673 //NSLog(@" found alternate server: %@", string);
2674 [alternateServerName release];
2675 alternateServerName = [alt copy];
2679 // Try alternate server again...
2680 if (!svrConn && alternateServerName) {
2681 //NSLog(@" trying to connect to alternate server: %@",
2682 // alternateServerName);
2683 connName = [self connectionNameFromServerName:alternateServerName];
2684 svrConn = [NSConnection connectionWithRegisteredName:connName
2689 [connectionNameDict setObject:svrConn forKey:connName];
2691 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2692 [[NSNotificationCenter defaultCenter] addObserver:self
2693 selector:@selector(serverConnectionDidDie:)
2694 name:NSConnectionDidDieNotification object:svrConn];
2701 - (NSConnection *)connectionForServerPort:(int)port
2704 NSEnumerator *e = [connectionNameDict objectEnumerator];
2706 while ((conn = [e nextObject])) {
2707 // HACK! Assume connection uses mach ports.
2708 if (port == [(NSMachPort*)[conn sendPort] machPort])
2715 - (void)serverConnectionDidDie:(NSNotification *)notification
2717 //NSLog(@"%s%@", _cmd, notification);
2719 NSConnection *svrConn = [notification object];
2721 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2722 [[NSNotificationCenter defaultCenter]
2724 name:NSConnectionDidDieNotification
2727 [connectionNameDict removeObjectsForKeys:
2728 [connectionNameDict allKeysForObject:svrConn]];
2730 // HACK! Assume connection uses mach ports.
2731 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2732 NSNumber *key = [NSNumber numberWithInt:port];
2734 [clientProxyDict removeObjectForKey:key];
2735 [serverReplyDict removeObjectForKey:key];
2738 - (void)addClient:(NSDistantObject *)client
2740 NSConnection *conn = [client connectionForProxy];
2741 // HACK! Assume connection uses mach ports.
2742 int port = [(NSMachPort*)[conn sendPort] machPort];
2743 NSNumber *key = [NSNumber numberWithInt:port];
2745 if (![clientProxyDict objectForKey:key]) {
2746 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2747 [clientProxyDict setObject:client forKey:key];
2750 // NOTE: 'clientWindow' is a global variable which is used by <client>
2751 clientWindow = port;
2754 - (NSString *)alternateServerNameForName:(NSString *)name
2756 if (!(name && [name length] > 0))
2759 // Only look for alternates if 'name' doesn't end in a digit.
2760 unichar lastChar = [name characterAtIndex:[name length]-1];
2761 if (lastChar >= '0' && lastChar <= '9')
2764 // Look for alternates among all current servers.
2765 NSArray *list = [self serverList];
2766 if (!(list && [list count] > 0))
2769 // Filter out servers starting with 'name' and ending with a number. The
2770 // (?i) pattern ensures that the match is case insensitive.
2771 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2772 NSPredicate *pred = [NSPredicate predicateWithFormat:
2773 @"SELF MATCHES %@", pat];
2774 list = [list filteredArrayUsingPredicate:pred];
2775 if ([list count] > 0) {
2776 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2777 return [list objectAtIndex:0];
2783 @end // MMBackend (ClientServer)
2788 @implementation NSString (MMServerNameCompare)
2789 - (NSComparisonResult)serverNameCompare:(NSString *)string
2791 return [self compare:string
2792 options:NSCaseInsensitiveSearch|NSNumericSearch];
2799 static int eventModifierFlagsToVimModMask(int modifierFlags)
2803 if (modifierFlags & NSShiftKeyMask)
2804 modMask |= MOD_MASK_SHIFT;
2805 if (modifierFlags & NSControlKeyMask)
2806 modMask |= MOD_MASK_CTRL;
2807 if (modifierFlags & NSAlternateKeyMask)
2808 modMask |= MOD_MASK_ALT;
2809 if (modifierFlags & NSCommandKeyMask)
2810 modMask |= MOD_MASK_CMD;
2815 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2819 if (modifierFlags & NSShiftKeyMask)
2820 modMask |= MOUSE_SHIFT;
2821 if (modifierFlags & NSControlKeyMask)
2822 modMask |= MOUSE_CTRL;
2823 if (modifierFlags & NSAlternateKeyMask)
2824 modMask |= MOUSE_ALT;
2829 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2831 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2833 return (buttonNumber >= 0 && buttonNumber < 3)
2834 ? mouseButton[buttonNumber] : -1;
2839 // This function is modeled after the VimToPython function found in if_python.c
2840 // NB This does a deep copy by value, it does not lookup references like the
2841 // VimToPython function does. This is because I didn't want to deal with the
2842 // retain cycles that this would create, and we can cover 99% of the use cases
2843 // by ignoring it. If we ever switch to using GC in MacVim then this
2844 // functionality can be implemented easily.
2845 static id vimToCocoa(typval_T * tv, int depth)
2851 // Avoid infinite recursion
2856 if (tv->v_type == VAR_STRING) {
2857 char_u * val = tv->vval.v_string;
2858 // val can be NULL if the string is empty
2860 result = [NSString string];
2863 val = CONVERT_TO_UTF8(val);
2865 result = [NSString stringWithUTF8String:(char*)val];
2867 CONVERT_TO_UTF8_FREE(val);
2870 } else if (tv->v_type == VAR_NUMBER) {
2871 // looks like sizeof(varnumber_T) is always <= sizeof(long)
2872 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2873 } else if (tv->v_type == VAR_LIST) {
2874 list_T * list = tv->vval.v_list;
2877 NSMutableArray * arr = result = [NSMutableArray array];
2880 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2881 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2882 [arr addObject:newObj];
2885 } else if (tv->v_type == VAR_DICT) {
2886 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2888 if (tv->vval.v_dict != NULL) {
2889 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2890 int todo = ht->ht_used;
2894 for (hi = ht->ht_array; todo > 0; ++hi) {
2895 if (!HASHITEM_EMPTY(hi)) {
2898 di = dict_lookup(hi);
2899 newObj = vimToCocoa(&di->di_tv, depth + 1);
2901 char_u * keyval = hi->hi_key;
2903 keyval = CONVERT_TO_UTF8(keyval);
2905 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2907 CONVERT_TO_UTF8_FREE(keyval);
2909 [dict setObject:newObj forKey:key];
2913 } else { // only func refs should fall into this category?
2921 // This function is modeled after eval_client_expr_to_string found in main.c
2922 // Returns nil if there was an error evaluating the expression, and writes a
2923 // message to errorStr.
2924 // TODO Get the error that occurred while evaluating the expression in vim
2926 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2929 char_u *s = (char_u*)[expr UTF8String];
2932 s = CONVERT_FROM_UTF8(s);
2935 int save_dbl = debug_break_level;
2936 int save_ro = redir_off;
2938 debug_break_level = -1;
2942 typval_T * tvres = eval_expr(s, NULL);
2944 debug_break_level = save_dbl;
2945 redir_off = save_ro;
2952 CONVERT_FROM_UTF8_FREE(s);
2957 gui_update_cursor(FALSE, FALSE);
2960 if (tvres == NULL) {
2962 *errstr = @"Expression evaluation failed.";
2965 id res = vimToCocoa(tvres, 1);
2970 *errstr = @"Conversion to cocoa values failed.";
2978 @implementation NSString (VimStrings)
2980 + (id)stringWithVimString:(char_u *)s
2982 // This method ensures a non-nil string is returned. If 's' cannot be
2983 // converted to a utf-8 string it is assumed to be latin-1. If conversion
2984 // still fails an empty NSString is returned.
2985 NSString *string = nil;
2988 s = CONVERT_TO_UTF8(s);
2990 string = [NSString stringWithUTF8String:(char*)s];
2992 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2994 string = [NSString stringWithCString:(char*)s
2995 encoding:NSISOLatin1StringEncoding];
2998 CONVERT_TO_UTF8_FREE(s);
3002 return string != nil ? string : [NSString string];
3005 - (char_u *)vimStringSave
3007 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3010 s = CONVERT_FROM_UTF8(s);
3012 ret = vim_strsave(s);
3014 CONVERT_FROM_UTF8_FREE(s);
3020 @end // NSString (VimStrings)