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);
54 // Before exiting process, sleep for this many microseconds. This is to allow
55 // any distributed object messages in transit to be received by MacVim before
56 // the process dies (otherwise an error message is logged by Cocoa). Note that
57 // this delay is only necessary if an NSConnection to MacVim has been
59 static useconds_t MMExitProcessDelay = 300000;
62 vimmenu_T *menu_for_descriptor(NSArray *desc);
64 static id evalExprCocoa(NSString * expr, NSString ** errstr);
73 static NSString *MMSymlinkWarningString =
74 @"\n\n\tMost likely this is because you have symlinked directly to\n"
75 "\tthe Vim binary, which Cocoa does not allow. Please use an\n"
76 "\talias or the mvim shell script instead. If you have not used\n"
77 "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
81 @interface NSString (MMServerNameCompare)
82 - (NSComparisonResult)serverNameCompare:(NSString *)string;
88 @interface MMBackend (Private)
89 - (void)waitForDialogReturn;
90 - (void)insertVimStateMessage;
91 - (void)processInputQueue;
92 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
93 + (NSDictionary *)specialKeys;
94 - (void)handleInsertText:(NSData *)data;
95 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
96 - (void)queueMessage:(int)msgid data:(NSData *)data;
97 - (void)connectionDidDie:(NSNotification *)notification;
98 - (void)blinkTimerFired:(NSTimer *)timer;
99 - (void)focusChange:(BOOL)on;
100 - (void)handleToggleToolbar;
101 - (void)handleScrollbarEvent:(NSData *)data;
102 - (void)handleSetFont:(NSData *)data;
103 - (void)handleDropFiles:(NSData *)data;
104 - (void)handleDropString:(NSData *)data;
105 - (void)startOdbEditWithArguments:(NSDictionary *)args;
106 - (void)handleXcodeMod:(NSData *)data;
107 - (void)handleOpenWithArguments:(NSDictionary *)args;
108 - (BOOL)checkForModifiedBuffers;
109 - (void)addInput:(NSString *)input;
110 - (BOOL)unusedEditor;
111 - (void)redrawScreen;
116 @interface MMBackend (ClientServer)
117 - (NSString *)connectionNameFromServerName:(NSString *)name;
118 - (NSConnection *)connectionForServerName:(NSString *)name;
119 - (NSConnection *)connectionForServerPort:(int)port;
120 - (void)serverConnectionDidDie:(NSNotification *)notification;
121 - (void)addClient:(NSDistantObject *)client;
122 - (NSString *)alternateServerNameForName:(NSString *)name;
127 @implementation MMBackend
129 + (MMBackend *)sharedInstance
131 static MMBackend *singleton = nil;
132 return singleton ? singleton : (singleton = [MMBackend new]);
138 if (!self) return nil;
140 fontContainerRef = loadFonts();
142 outputQueue = [[NSMutableArray alloc] init];
143 inputQueue = [[NSMutableArray alloc] init];
144 drawData = [[NSMutableData alloc] initWithCapacity:1024];
145 connectionNameDict = [[NSMutableDictionary alloc] init];
146 clientProxyDict = [[NSMutableDictionary alloc] init];
147 serverReplyDict = [[NSMutableDictionary alloc] init];
149 NSBundle *mainBundle = [NSBundle mainBundle];
150 NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
152 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
154 path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
156 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
159 path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
161 actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
163 if (!(colorDict && sysColorDict && actionDict))
164 NSLog(@"ERROR: Failed to load dictionaries.%@",
165 MMSymlinkWarningString);
172 //NSLog(@"%@ %s", [self className], _cmd);
173 [[NSNotificationCenter defaultCenter] removeObserver:self];
175 [oldWideFont release]; oldWideFont = nil;
176 [blinkTimer release]; blinkTimer = nil;
177 [alternateServerName release]; alternateServerName = nil;
178 [serverReplyDict release]; serverReplyDict = nil;
179 [clientProxyDict release]; clientProxyDict = nil;
180 [connectionNameDict release]; connectionNameDict = nil;
181 [inputQueue release]; inputQueue = nil;
182 [outputQueue release]; outputQueue = nil;
183 [drawData release]; drawData = nil;
184 [frontendProxy release]; frontendProxy = nil;
185 [connection release]; connection = nil;
186 [actionDict release]; actionDict = nil;
187 [sysColorDict release]; sysColorDict = nil;
188 [colorDict release]; colorDict = nil;
193 - (void)setBackgroundColor:(int)color
195 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
198 - (void)setForegroundColor:(int)color
200 foregroundColor = MM_COLOR(color);
203 - (void)setSpecialColor:(int)color
205 specialColor = MM_COLOR(color);
208 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
210 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
211 defaultForegroundColor = MM_COLOR(fg);
213 NSMutableData *data = [NSMutableData data];
215 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
216 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
218 [self queueMessage:SetDefaultColorsMsgID data:data];
221 - (NSConnection *)connection
224 // NOTE! If the name of the connection changes here it must also be
225 // updated in MMAppController.m.
226 NSString *name = [NSString stringWithFormat:@"%@-connection",
227 [[NSBundle mainBundle] bundlePath]];
229 connection = [NSConnection connectionWithRegisteredName:name host:nil];
233 // NOTE: 'connection' may be nil here.
237 - (NSDictionary *)actionDict
242 - (int)initialWindowLayout
244 return initialWindowLayout;
247 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
249 [self queueMessage:msgid data:[props dictionaryAsData]];
254 if (![self connection]) {
256 // This is a preloaded process and as such should not cause the
257 // MacVim to be opened. We probably got here as a result of the
258 // user quitting MacVim while the process was preloading, so exit
260 // (Don't use mch_exit() since it assumes the process has properly
265 NSBundle *mainBundle = [NSBundle mainBundle];
270 // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
271 // the API to pass Apple Event parameters is broken on 10.4).
272 NSString *path = [mainBundle bundlePath];
273 status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
274 if (noErr == status) {
275 // Pass parameter to the 'Open' Apple Event that tells MacVim not
276 // to open an untitled window.
277 NSAppleEventDescriptor *desc =
278 [NSAppleEventDescriptor recordDescriptor];
279 [desc setParamDescriptor:
280 [NSAppleEventDescriptor descriptorWithBoolean:NO]
281 forKeyword:keyMMUntitledWindow];
283 LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
284 kLSLaunchDefaults, NULL };
285 status = LSOpenFromRefSpec(&spec, NULL);
288 if (noErr != status) {
289 NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
290 path, MMSymlinkWarningString);
294 // Launch MacVim using NSTask. For some reason the above code using
295 // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
296 // fails, the dock icon starts bouncing and never stops). It seems
297 // like rebuilding the Launch Services database takes care of this
298 // problem, but the NSTask way seems more stable so stick with it.
300 // NOTE! Using NSTask to launch the GUI has the negative side-effect
301 // that the GUI won't be activated (or raised) so there is a hack in
302 // MMAppController which raises the app when a new window is opened.
303 NSMutableArray *args = [NSMutableArray arrayWithObjects:
304 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
305 NSString *exeName = [[mainBundle infoDictionary]
306 objectForKey:@"CFBundleExecutable"];
307 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
309 NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
310 MMSymlinkWarningString);
314 [NSTask launchedTaskWithLaunchPath:path arguments:args];
317 // HACK! Poll the mach bootstrap server until it returns a valid
318 // connection to detect that MacVim has finished launching. Also set a
319 // time-out date so that we don't get stuck doing this forever.
320 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
321 while (![self connection] &&
322 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
323 [[NSRunLoop currentRunLoop]
324 runMode:NSDefaultRunLoopMode
325 beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
327 // NOTE: [self connection] will set 'connection' as a side-effect.
329 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
336 [[NSNotificationCenter defaultCenter] addObserver:self
337 selector:@selector(connectionDidDie:)
338 name:NSConnectionDidDieNotification object:connection];
340 id proxy = [connection rootProxy];
341 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
343 int pid = [[NSProcessInfo processInfo] processIdentifier];
345 frontendProxy = [proxy connectBackend:self pid:pid];
347 [frontendProxy retain];
348 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
352 @catch (NSException *e) {
353 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
359 - (BOOL)openGUIWindow
361 [self queueMessage:OpenWindowMsgID data:nil];
367 int type = ClearAllDrawType;
369 // Any draw commands in queue are effectively obsolete since this clearAll
370 // will negate any effect they have, therefore we may as well clear the
372 [drawData setLength:0];
374 [drawData appendBytes:&type length:sizeof(int)];
377 - (void)clearBlockFromRow:(int)row1 column:(int)col1
378 toRow:(int)row2 column:(int)col2
380 int type = ClearBlockDrawType;
382 [drawData appendBytes:&type length:sizeof(int)];
384 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
385 [drawData appendBytes:&row1 length:sizeof(int)];
386 [drawData appendBytes:&col1 length:sizeof(int)];
387 [drawData appendBytes:&row2 length:sizeof(int)];
388 [drawData appendBytes:&col2 length:sizeof(int)];
391 - (void)deleteLinesFromRow:(int)row count:(int)count
392 scrollBottom:(int)bottom left:(int)left right:(int)right
394 int type = DeleteLinesDrawType;
396 [drawData appendBytes:&type length:sizeof(int)];
398 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
399 [drawData appendBytes:&row length:sizeof(int)];
400 [drawData appendBytes:&count length:sizeof(int)];
401 [drawData appendBytes:&bottom length:sizeof(int)];
402 [drawData appendBytes:&left length:sizeof(int)];
403 [drawData appendBytes:&right length:sizeof(int)];
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)];
441 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
442 fraction:(int)percent color:(int)color
444 int type = DrawCursorDrawType;
445 unsigned uc = MM_COLOR(color);
447 [drawData appendBytes:&type length:sizeof(int)];
449 [drawData appendBytes:&uc length:sizeof(unsigned)];
450 [drawData appendBytes:&row length:sizeof(int)];
451 [drawData appendBytes:&col length:sizeof(int)];
452 [drawData appendBytes:&shape length:sizeof(int)];
453 [drawData appendBytes:&percent length:sizeof(int)];
456 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
457 numColumns:(int)nc invert:(int)invert
459 int type = DrawInvertedRectDrawType;
460 [drawData appendBytes:&type length:sizeof(int)];
462 [drawData appendBytes:&row length:sizeof(int)];
463 [drawData appendBytes:&col length:sizeof(int)];
464 [drawData appendBytes:&nr length:sizeof(int)];
465 [drawData appendBytes:&nc length:sizeof(int)];
466 [drawData appendBytes:&invert length:sizeof(int)];
471 // Keep running the run-loop until there is no more input to process.
472 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
473 == kCFRunLoopRunHandledSource)
477 - (void)flushQueue:(BOOL)force
479 // NOTE: This variable allows for better control over when the queue is
480 // flushed. It can be set to YES at the beginning of a sequence of calls
481 // that may potentially add items to the queue, and then restored back to
483 if (flushDisabled) return;
485 if ([drawData length] > 0) {
486 // HACK! Detect changes to 'guifontwide'.
487 if (gui.wide_font != (GuiFont)oldWideFont) {
488 [oldWideFont release];
489 oldWideFont = [(NSFont*)gui.wide_font retain];
490 [self setWideFont:oldWideFont];
493 int type = SetCursorPosDrawType;
494 [drawData appendBytes:&type length:sizeof(type)];
495 [drawData appendBytes:&gui.row length:sizeof(gui.row)];
496 [drawData appendBytes:&gui.col length:sizeof(gui.col)];
498 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
499 [drawData setLength:0];
502 if ([outputQueue count] > 0) {
503 [self insertVimStateMessage];
506 [frontendProxy processCommandQueue:outputQueue];
508 @catch (NSException *e) {
509 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
512 [outputQueue removeAllObjects];
516 - (BOOL)waitForInput:(int)milliseconds
518 // Return NO if we timed out waiting for input, otherwise return YES.
519 BOOL inputReceived = NO;
521 // Only start the run loop if the input queue is empty, otherwise process
522 // the input first so that the input on queue isn't delayed.
523 if ([inputQueue count]) {
526 // Wait for the specified amount of time, unless 'milliseconds' is
527 // negative in which case we wait "forever" (1e6 seconds translates to
528 // approximately 11 days).
529 CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
531 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
532 == kCFRunLoopRunHandledSource) {
533 // In order to ensure that all input on the run-loop has been
534 // processed we set the timeout to 0 and keep processing until the
535 // run-loop times out.
541 // The above calls may have placed messages on the input queue so process
542 // it now. This call may enter a blocking loop.
543 if ([inputQueue count] > 0)
544 [self processInputQueue];
546 return inputReceived;
551 // NOTE: This is called if mch_exit() is called. Since we assume here that
552 // the process has started properly, be sure to use exit() instead of
553 // mch_exit() to prematurely terminate a process.
555 // To notify MacVim that this Vim process is exiting we could simply
556 // invalidate the connection and it would automatically receive a
557 // connectionDidDie: notification. However, this notification seems to
558 // take up to 300 ms to arrive which is quite a noticeable delay. Instead
559 // we immediately send a message to MacVim asking it to close the window
560 // belonging to this process, and then we invalidate the connection (in
561 // case the message got lost).
563 // Make sure no connectionDidDie: notification is received now that we are
565 [[NSNotificationCenter defaultCenter] removeObserver:self];
567 if ([connection isValid]) {
569 // Flush the entire queue in case a VimLeave autocommand added
570 // something to the queue.
571 [self queueMessage:CloseWindowMsgID data:nil];
572 [frontendProxy processCommandQueue:outputQueue];
574 @catch (NSException *e) {
575 NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
578 [connection invalidate];
581 #ifdef MAC_CLIENTSERVER
582 // The default connection is used for the client/server code.
583 [[NSConnection defaultConnection] setRootObject:nil];
584 [[NSConnection defaultConnection] invalidate];
587 if (fontContainerRef) {
588 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
589 fontContainerRef = 0;
592 usleep(MMExitProcessDelay);
595 - (void)selectTab:(int)index
597 //NSLog(@"%s%d", _cmd, index);
600 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
601 [self queueMessage:SelectTabMsgID data:data];
606 //NSLog(@"%s", _cmd);
608 NSMutableData *data = [NSMutableData data];
610 int idx = tabpage_index(curtab) - 1;
611 [data appendBytes:&idx length:sizeof(int)];
614 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
615 // This function puts the label of the tab in the global 'NameBuff'.
616 get_tabline_label(tp, FALSE);
617 char_u *s = NameBuff;
619 if (len <= 0) continue;
622 s = CONVERT_TO_UTF8(s);
625 // Count the number of windows in the tabpage.
626 //win_T *wp = tp->tp_firstwin;
628 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
630 //[data appendBytes:&wincount length:sizeof(int)];
631 [data appendBytes:&len length:sizeof(int)];
632 [data appendBytes:s length:len];
635 CONVERT_TO_UTF8_FREE(s);
639 [self queueMessage:UpdateTabBarMsgID data:data];
642 - (BOOL)tabBarVisible
644 return tabBarVisible;
647 - (void)showTabBar:(BOOL)enable
649 tabBarVisible = enable;
651 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
652 [self queueMessage:msgid data:nil];
655 - (void)setRows:(int)rows columns:(int)cols
657 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
659 int dim[] = { rows, cols };
660 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
662 [self queueMessage:SetTextDimensionsMsgID data:data];
665 - (void)setWindowTitle:(char *)title
667 NSMutableData *data = [NSMutableData data];
668 int len = strlen(title);
669 if (len <= 0) return;
671 [data appendBytes:&len length:sizeof(int)];
672 [data appendBytes:title length:len];
674 [self queueMessage:SetWindowTitleMsgID data:data];
677 - (void)setDocumentFilename:(char *)filename
679 NSMutableData *data = [NSMutableData data];
680 int len = filename ? strlen(filename) : 0;
682 [data appendBytes:&len length:sizeof(int)];
684 [data appendBytes:filename length:len];
686 [self queueMessage:SetDocumentFilenameMsgID data:data];
689 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
694 [frontendProxy showSavePanelWithAttributes:attr];
696 [self waitForDialogReturn];
698 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
699 char_u *ret = (char_u*)[dialogReturn UTF8String];
701 ret = CONVERT_FROM_UTF8(ret);
703 s = vim_strsave(ret);
705 CONVERT_FROM_UTF8_FREE(ret);
709 [dialogReturn release]; dialogReturn = nil;
711 @catch (NSException *e) {
712 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
718 - (oneway void)setDialogReturn:(in bycopy id)obj
720 // NOTE: This is called by
721 // - [MMVimController panelDidEnd:::], and
722 // - [MMVimController alertDidEnd:::],
723 // to indicate that a save/open panel or alert has finished.
725 // We want to distinguish between "no dialog return yet" and "dialog
726 // returned nothing". The former can be tested with dialogReturn == nil,
727 // the latter with dialogReturn == [NSNull null].
728 if (!obj) obj = [NSNull null];
730 if (obj != dialogReturn) {
731 [dialogReturn release];
732 dialogReturn = [obj retain];
736 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
741 [frontendProxy presentDialogWithAttributes:attr];
743 [self waitForDialogReturn];
745 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
746 && [dialogReturn count]) {
747 retval = [[dialogReturn objectAtIndex:0] intValue];
748 if (txtfield && [dialogReturn count] > 1) {
749 NSString *retString = [dialogReturn objectAtIndex:1];
750 char_u *ret = (char_u*)[retString UTF8String];
752 ret = CONVERT_FROM_UTF8(ret);
754 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
756 CONVERT_FROM_UTF8_FREE(ret);
761 [dialogReturn release]; dialogReturn = nil;
763 @catch (NSException *e) {
764 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
770 - (void)showToolbar:(int)enable flags:(int)flags
772 NSMutableData *data = [NSMutableData data];
774 [data appendBytes:&enable length:sizeof(int)];
775 [data appendBytes:&flags length:sizeof(int)];
777 [self queueMessage:ShowToolbarMsgID data:data];
780 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
782 NSMutableData *data = [NSMutableData data];
784 [data appendBytes:&ident length:sizeof(long)];
785 [data appendBytes:&type length:sizeof(int)];
787 [self queueMessage:CreateScrollbarMsgID data:data];
790 - (void)destroyScrollbarWithIdentifier:(long)ident
792 NSMutableData *data = [NSMutableData data];
793 [data appendBytes:&ident length:sizeof(long)];
795 [self queueMessage:DestroyScrollbarMsgID data:data];
798 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
800 NSMutableData *data = [NSMutableData data];
802 [data appendBytes:&ident length:sizeof(long)];
803 [data appendBytes:&visible length:sizeof(int)];
805 [self queueMessage:ShowScrollbarMsgID data:data];
808 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
810 NSMutableData *data = [NSMutableData data];
812 [data appendBytes:&ident length:sizeof(long)];
813 [data appendBytes:&pos length:sizeof(int)];
814 [data appendBytes:&len length:sizeof(int)];
816 [self queueMessage:SetScrollbarPositionMsgID data:data];
819 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
820 identifier:(long)ident
822 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
823 float prop = (float)size/(max+1);
824 if (fval < 0) fval = 0;
825 else if (fval > 1.0f) fval = 1.0f;
826 if (prop < 0) prop = 0;
827 else if (prop > 1.0f) prop = 1.0f;
829 NSMutableData *data = [NSMutableData data];
831 [data appendBytes:&ident length:sizeof(long)];
832 [data appendBytes:&fval length:sizeof(float)];
833 [data appendBytes:&prop length:sizeof(float)];
835 [self queueMessage:SetScrollbarThumbMsgID data:data];
838 - (void)setFont:(NSFont *)font
840 NSString *fontName = [font displayName];
841 float size = [font pointSize];
842 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
844 NSMutableData *data = [NSMutableData data];
846 [data appendBytes:&size length:sizeof(float)];
847 [data appendBytes:&len length:sizeof(int)];
848 [data appendBytes:[fontName UTF8String] length:len];
850 [self queueMessage:SetFontMsgID data:data];
854 - (void)setWideFont:(NSFont *)font
856 NSString *fontName = [font displayName];
857 float size = [font pointSize];
858 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
859 NSMutableData *data = [NSMutableData data];
861 [data appendBytes:&size length:sizeof(float)];
862 [data appendBytes:&len length:sizeof(int)];
864 [data appendBytes:[fontName UTF8String] length:len];
866 [self queueMessage:SetWideFontMsgID data:data];
869 - (void)executeActionWithName:(NSString *)name
871 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
874 NSMutableData *data = [NSMutableData data];
876 [data appendBytes:&len length:sizeof(int)];
877 [data appendBytes:[name UTF8String] length:len];
879 [self queueMessage:ExecuteActionMsgID data:data];
883 - (void)setMouseShape:(int)shape
885 NSMutableData *data = [NSMutableData data];
886 [data appendBytes:&shape length:sizeof(int)];
887 [self queueMessage:SetMouseShapeMsgID data:data];
890 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
892 // Vim specifies times in milliseconds, whereas Cocoa wants them in
894 blinkWaitInterval = .001f*wait;
895 blinkOnInterval = .001f*on;
896 blinkOffInterval = .001f*off;
902 [blinkTimer invalidate];
903 [blinkTimer release];
907 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
909 blinkState = MMBlinkStateOn;
911 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
913 selector:@selector(blinkTimerFired:)
914 userInfo:nil repeats:NO] retain];
915 gui_update_cursor(TRUE, FALSE);
916 [self flushQueue:YES];
922 if (MMBlinkStateOff == blinkState) {
923 gui_update_cursor(TRUE, FALSE);
924 [self flushQueue:YES];
927 blinkState = MMBlinkStateNone;
930 - (void)adjustLinespace:(int)linespace
932 NSMutableData *data = [NSMutableData data];
933 [data appendBytes:&linespace length:sizeof(int)];
934 [self queueMessage:AdjustLinespaceMsgID data:data];
939 [self queueMessage:ActivateMsgID data:nil];
942 - (void)setPreEditRow:(int)row column:(int)col
944 NSMutableData *data = [NSMutableData data];
945 [data appendBytes:&row length:sizeof(int)];
946 [data appendBytes:&col length:sizeof(int)];
947 [self queueMessage:SetPreEditPositionMsgID data:data];
950 - (int)lookupColorWithKey:(NSString *)key
952 if (!(key && [key length] > 0))
955 NSString *stripKey = [[[[key lowercaseString]
956 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
957 componentsSeparatedByString:@" "]
958 componentsJoinedByString:@""];
960 if (stripKey && [stripKey length] > 0) {
961 // First of all try to lookup key in the color dictionary; note that
962 // all keys in this dictionary are lowercase with no whitespace.
963 id obj = [colorDict objectForKey:stripKey];
964 if (obj) return [obj intValue];
966 // The key was not in the dictionary; is it perhaps of the form
968 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
969 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
970 [scanner setScanLocation:1];
972 if ([scanner scanHexInt:&hex]) {
977 // As a last resort, check if it is one of the system defined colors.
978 // The keys in this dictionary are also lowercase with no whitespace.
979 obj = [sysColorDict objectForKey:stripKey];
981 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
984 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
985 [col getRed:&r green:&g blue:&b alpha:&a];
986 return (((int)(r*255+.5f) & 0xff) << 16)
987 + (((int)(g*255+.5f) & 0xff) << 8)
988 + ((int)(b*255+.5f) & 0xff);
993 //NSLog(@"WARNING: No color with key %@ found.", stripKey);
997 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
999 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1002 while ((obj = [e nextObject])) {
1003 if ([value isEqual:obj])
1010 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1012 NSMutableData *data = [NSMutableData data];
1013 [data appendBytes:&fuoptions length:sizeof(int)];
1015 [data appendBytes:&bg length:sizeof(int)];
1016 [self queueMessage:EnterFullscreenMsgID data:data];
1019 - (void)leaveFullscreen
1021 [self queueMessage:LeaveFullscreenMsgID data:nil];
1024 - (void)setFullscreenBackgroundColor:(int)color
1026 NSMutableData *data = [NSMutableData data];
1027 color = MM_COLOR(color);
1028 [data appendBytes:&color length:sizeof(int)];
1030 [self queueMessage:SetFullscreenColorMsgID data:data];
1033 - (void)setAntialias:(BOOL)antialias
1035 int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1037 [self queueMessage:msgid data:nil];
1040 - (void)updateModifiedFlag
1042 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1044 int msgid = [self checkForModifiedBuffers]
1045 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1047 [self queueMessage:msgid data:nil];
1050 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1052 // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1053 // queue is processed since that only happens in waitForInput: (and Vim
1054 // regularly checks for Ctrl-C in between waiting for input).
1056 BOOL interrupt = NO;
1057 if (msgid == InterruptMsgID) {
1059 } else if (InsertTextMsgID == msgid && data != nil && [data length] == 1) {
1060 char_u *str = (char_u*)[data bytes];
1061 if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1062 (str[0] == intr_char && intr_char != Ctrl_C))
1068 [inputQueue removeAllObjects];
1072 // Remove all previous instances of this message from the input queue, else
1073 // the input queue may fill up as a result of Vim not being able to keep up
1074 // with the speed at which new messages are received. This avoids annoying
1075 // situations such as when the keyboard repeat rate is higher than what Vim
1076 // can cope with (which would cause a 'stutter' when scrolling by holding
1077 // down 'j' and then when 'j' was released the screen kept scrolling for a
1080 int i, count = [inputQueue count];
1081 for (i = 1; i < count; i+=2) {
1082 if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1083 [inputQueue removeObjectAtIndex:i];
1084 [inputQueue removeObjectAtIndex:i-1];
1089 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1090 [inputQueue addObject:(data ? (id)data : [NSNull null])];
1093 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1095 // This is just a convenience method that allows the frontend to delay
1096 // sending messages.
1097 int i, count = [messages count];
1098 for (i = 1; i < count; i+=2)
1099 [self processInput:[[messages objectAtIndex:i-1] intValue]
1100 data:[messages objectAtIndex:i]];
1103 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1104 errorString:(out bycopy NSString **)errstr
1106 return evalExprCocoa(expr, errstr);
1110 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1112 NSString *eval = nil;
1113 char_u *s = (char_u*)[expr UTF8String];
1116 s = CONVERT_FROM_UTF8(s);
1119 char_u *res = eval_client_expr_to_string(s);
1122 CONVERT_FROM_UTF8_FREE(s);
1128 s = CONVERT_TO_UTF8(s);
1130 eval = [NSString stringWithUTF8String:(char*)s];
1132 CONVERT_TO_UTF8_FREE(s);
1140 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1142 // TODO: This method should share code with clip_mch_request_selection().
1144 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1145 // If there is no pasteboard, return YES to indicate that there is text
1150 clip_copy_selection();
1152 // Get the text to put on the pasteboard.
1153 long_u llen = 0; char_u *str = 0;
1154 int type = clip_convert_selection(&str, &llen, &clip_star);
1158 // TODO: Avoid overflow.
1159 int len = (int)llen;
1161 if (output_conv.vc_type != CONV_NONE) {
1162 char_u *conv_str = string_convert(&output_conv, str, &len);
1170 NSString *string = [[NSString alloc]
1171 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1173 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1174 [pboard declareTypes:types owner:nil];
1175 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1186 - (oneway void)addReply:(in bycopy NSString *)reply
1187 server:(in byref id <MMVimServerProtocol>)server
1189 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1191 // Replies might come at any time and in any order so we keep them in an
1192 // array inside a dictionary with the send port used as key.
1194 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1195 // HACK! Assume connection uses mach ports.
1196 int port = [(NSMachPort*)[conn sendPort] machPort];
1197 NSNumber *key = [NSNumber numberWithInt:port];
1199 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1201 replies = [NSMutableArray array];
1202 [serverReplyDict setObject:replies forKey:key];
1205 [replies addObject:reply];
1208 - (void)addInput:(in bycopy NSString *)input
1209 client:(in byref id <MMVimClientProtocol>)client
1211 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1213 [self addInput:input];
1214 [self addClient:(id)client];
1217 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1218 client:(in byref id <MMVimClientProtocol>)client
1220 [self addClient:(id)client];
1221 return [self evaluateExpression:expr];
1224 - (void)registerServerWithName:(NSString *)name
1226 NSString *svrName = name;
1227 NSConnection *svrConn = [NSConnection defaultConnection];
1230 for (i = 0; i < MMServerMax; ++i) {
1231 NSString *connName = [self connectionNameFromServerName:svrName];
1233 if ([svrConn registerName:connName]) {
1234 //NSLog(@"Registered server with name: %@", svrName);
1236 // TODO: Set request/reply time-outs to something else?
1238 // Don't wait for requests (time-out means that the message is
1240 [svrConn setRequestTimeout:0];
1241 //[svrConn setReplyTimeout:MMReplyTimeout];
1242 [svrConn setRootObject:self];
1244 char_u *s = (char_u*)[svrName UTF8String];
1246 s = CONVERT_FROM_UTF8(s);
1248 // NOTE: 'serverName' is a global variable
1249 serverName = vim_strsave(s);
1251 CONVERT_FROM_UTF8_FREE(s);
1254 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1257 need_maketitle = TRUE;
1259 [self queueMessage:SetServerNameMsgID data:
1260 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1264 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1268 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1269 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1272 // NOTE: If 'name' equals 'serverName' then the request is local (client
1273 // and server are the same). This case is not handled separately, so a
1274 // connection will be set up anyway (this simplifies the code).
1276 NSConnection *conn = [self connectionForServerName:name];
1279 char_u *s = (char_u*)[name UTF8String];
1281 s = CONVERT_FROM_UTF8(s);
1283 EMSG2(_(e_noserver), s);
1285 CONVERT_FROM_UTF8_FREE(s);
1292 // HACK! Assume connection uses mach ports.
1293 *port = [(NSMachPort*)[conn sendPort] machPort];
1296 id proxy = [conn rootProxy];
1297 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1301 NSString *eval = [proxy evaluateExpression:string client:self];
1304 char_u *r = (char_u*)[eval UTF8String];
1306 r = CONVERT_FROM_UTF8(r);
1308 *reply = vim_strsave(r);
1310 CONVERT_FROM_UTF8_FREE(r);
1313 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1320 [proxy addInput:string client:self];
1323 @catch (NSException *e) {
1324 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1331 - (NSArray *)serverList
1333 NSArray *list = nil;
1335 if ([self connection]) {
1336 id proxy = [connection rootProxy];
1337 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1340 list = [proxy serverList];
1342 @catch (NSException *e) {
1343 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1346 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1352 - (NSString *)peekForReplyOnPort:(int)port
1354 //NSLog(@"%s%d", _cmd, port);
1356 NSNumber *key = [NSNumber numberWithInt:port];
1357 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1358 if (replies && [replies count]) {
1359 //NSLog(@" %d replies, topmost is: %@", [replies count],
1360 // [replies objectAtIndex:0]);
1361 return [replies objectAtIndex:0];
1364 //NSLog(@" No replies");
1368 - (NSString *)waitForReplyOnPort:(int)port
1370 //NSLog(@"%s%d", _cmd, port);
1372 NSConnection *conn = [self connectionForServerPort:port];
1376 NSNumber *key = [NSNumber numberWithInt:port];
1377 NSMutableArray *replies = nil;
1378 NSString *reply = nil;
1380 // Wait for reply as long as the connection to the server is valid (unless
1381 // user interrupts wait with Ctrl-C).
1382 while (!got_int && [conn isValid] &&
1383 !(replies = [serverReplyDict objectForKey:key])) {
1384 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1385 beforeDate:[NSDate distantFuture]];
1389 if ([replies count] > 0) {
1390 reply = [[replies objectAtIndex:0] retain];
1391 //NSLog(@" Got reply: %@", reply);
1392 [replies removeObjectAtIndex:0];
1393 [reply autorelease];
1396 if ([replies count] == 0)
1397 [serverReplyDict removeObjectForKey:key];
1403 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1405 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1408 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1409 [client addReply:reply server:self];
1412 @catch (NSException *e) {
1413 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1416 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1427 - (void)setWaitForAck:(BOOL)yn
1432 - (void)waitForConnectionAcknowledgement
1434 if (!waitForAck) return;
1436 while (waitForAck && !got_int && [connection isValid] && !isTerminating) {
1437 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1438 beforeDate:[NSDate distantFuture]];
1439 //NSLog(@" waitForAck=%d got_int=%d isTerminating=%d isValid=%d",
1440 // waitForAck, got_int, isTerminating, [connection isValid]);
1444 // Never received a connection acknowledgement, so die.
1445 [[NSNotificationCenter defaultCenter] removeObserver:self];
1446 [frontendProxy release]; frontendProxy = nil;
1448 // NOTE: We intentionally do not call mch_exit() since this in turn
1449 // will lead to -[MMBackend exit] getting called which we want to
1451 usleep(MMExitProcessDelay);
1455 [self processInputQueue];
1458 - (oneway void)acknowledgeConnection
1460 //NSLog(@"%s", _cmd);
1468 @implementation MMBackend (Private)
1470 - (void)waitForDialogReturn
1472 // Keep processing the run loop until a dialog returns. To avoid getting
1473 // stuck in an endless loop (could happen if the setDialogReturn: message
1474 // was lost) we also do some paranoia checks.
1476 // Note that in Cocoa the user can still resize windows and select menu
1477 // items while a sheet is being displayed, so we can't just wait for the
1478 // first message to arrive and assume that is the setDialogReturn: call.
1480 while (nil == dialogReturn && !got_int && [connection isValid]
1482 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1483 beforeDate:[NSDate distantFuture]];
1485 // Search for any resize messages on the input queue. All other messages
1486 // on the input queue are dropped. The reason why we single out resize
1487 // messages is because the user may have resized the window while a sheet
1489 int i, count = [inputQueue count];
1491 id textDimData = nil;
1493 for (i = count-2; i >= 0; i -= 2) {
1494 int msgid = [[inputQueue objectAtIndex:i] intValue];
1495 if (SetTextDimensionsMsgID == msgid) {
1496 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1502 [inputQueue removeAllObjects];
1505 [inputQueue addObject:
1506 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1507 [inputQueue addObject:textDimData];
1508 [textDimData release];
1513 - (void)insertVimStateMessage
1515 // NOTE: This is the place to add Vim state that needs to be accessed from
1516 // MacVim. Do not add state that could potentially require lots of memory
1517 // since this message gets sent each time the output queue is forcibly
1518 // flushed (e.g. storing the currently selected text would be a bad idea).
1519 // We take this approach of "pushing" the state to MacVim to avoid having
1520 // to make synchronous calls from MacVim to Vim in order to get state.
1522 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1523 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1524 [NSNumber numberWithInt:p_mh], @"p_mh",
1525 [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1528 // Put the state before all other messages.
1529 int msgid = SetVimStateMsgID;
1530 [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1531 [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1535 - (void)processInputQueue
1537 if ([inputQueue count] == 0) return;
1539 // NOTE: One of the input events may cause this method to be called
1540 // recursively, so copy the input queue to a local variable and clear the
1541 // queue before starting to process input events (otherwise we could get
1542 // stuck in an endless loop).
1543 NSArray *q = [inputQueue copy];
1544 unsigned i, count = [q count];
1546 [inputQueue removeAllObjects];
1548 for (i = 1; i < count; i+=2) {
1549 int msgid = [[q objectAtIndex:i-1] intValue];
1550 id data = [q objectAtIndex:i];
1551 if ([data isEqual:[NSNull null]])
1554 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1555 [self handleInputEvent:msgid data:data];
1559 //NSLog(@"Clear input event queue");
1562 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1564 if (InsertTextMsgID == msgid) {
1565 [self handleInsertText:data];
1566 } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1568 const void *bytes = [data bytes];
1569 int mods = *((int*)bytes); bytes += sizeof(int);
1570 int len = *((int*)bytes); bytes += sizeof(int);
1571 NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1572 encoding:NSUTF8StringEncoding];
1573 mods = eventModifierFlagsToVimModMask(mods);
1575 [self handleKeyDown:key modifiers:mods];
1578 } else if (ScrollWheelMsgID == msgid) {
1580 const void *bytes = [data bytes];
1582 int row = *((int*)bytes); bytes += sizeof(int);
1583 int col = *((int*)bytes); bytes += sizeof(int);
1584 int flags = *((int*)bytes); bytes += sizeof(int);
1585 float dy = *((float*)bytes); bytes += sizeof(float);
1587 int button = MOUSE_5;
1588 if (dy > 0) button = MOUSE_4;
1590 flags = eventModifierFlagsToVimMouseModMask(flags);
1592 int numLines = (int)round(dy);
1593 if (numLines < 0) numLines = -numLines;
1594 if (numLines == 0) numLines = 1;
1596 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1597 gui.scroll_wheel_force = numLines;
1600 gui_send_mouse_event(button, col, row, NO, flags);
1601 } else if (MouseDownMsgID == msgid) {
1603 const void *bytes = [data bytes];
1605 int row = *((int*)bytes); bytes += sizeof(int);
1606 int col = *((int*)bytes); bytes += sizeof(int);
1607 int button = *((int*)bytes); bytes += sizeof(int);
1608 int flags = *((int*)bytes); bytes += sizeof(int);
1609 int count = *((int*)bytes); bytes += sizeof(int);
1611 button = eventButtonNumberToVimMouseButton(button);
1613 flags = eventModifierFlagsToVimMouseModMask(flags);
1614 gui_send_mouse_event(button, col, row, count>1, flags);
1616 } else if (MouseUpMsgID == msgid) {
1618 const void *bytes = [data bytes];
1620 int row = *((int*)bytes); bytes += sizeof(int);
1621 int col = *((int*)bytes); bytes += sizeof(int);
1622 int flags = *((int*)bytes); bytes += sizeof(int);
1624 flags = eventModifierFlagsToVimMouseModMask(flags);
1626 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1627 } else if (MouseDraggedMsgID == msgid) {
1629 const void *bytes = [data bytes];
1631 int row = *((int*)bytes); bytes += sizeof(int);
1632 int col = *((int*)bytes); bytes += sizeof(int);
1633 int flags = *((int*)bytes); bytes += sizeof(int);
1635 flags = eventModifierFlagsToVimMouseModMask(flags);
1637 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1638 } else if (MouseMovedMsgID == msgid) {
1639 const void *bytes = [data bytes];
1640 int row = *((int*)bytes); bytes += sizeof(int);
1641 int col = *((int*)bytes); bytes += sizeof(int);
1643 gui_mouse_moved(col, row);
1644 } else if (AddInputMsgID == msgid) {
1645 NSString *string = [[NSString alloc] initWithData:data
1646 encoding:NSUTF8StringEncoding];
1648 [self addInput:string];
1651 } else if (TerminateNowMsgID == msgid) {
1652 isTerminating = YES;
1653 } else if (SelectTabMsgID == msgid) {
1655 const void *bytes = [data bytes];
1656 int idx = *((int*)bytes) + 1;
1657 //NSLog(@"Selecting tab %d", idx);
1658 send_tabline_event(idx);
1659 } else if (CloseTabMsgID == msgid) {
1661 const void *bytes = [data bytes];
1662 int idx = *((int*)bytes) + 1;
1663 //NSLog(@"Closing tab %d", idx);
1664 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1665 } else if (AddNewTabMsgID == msgid) {
1666 //NSLog(@"Adding new tab");
1667 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1668 } else if (DraggedTabMsgID == msgid) {
1670 const void *bytes = [data bytes];
1671 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1673 int idx = *((int*)bytes);
1676 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1677 || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1679 const void *bytes = [data bytes];
1681 if (SetTextColumnsMsgID != msgid) {
1682 rows = *((int*)bytes); bytes += sizeof(int);
1685 if (SetTextRowsMsgID != msgid) {
1686 cols = *((int*)bytes); bytes += sizeof(int);
1690 if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1691 int dim[2] = { rows, cols };
1692 d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1693 msgid = SetTextDimensionsMsgID;
1696 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1697 // gui_resize_shell(), so we have to manually set the rows and columns
1698 // here. (MacVim doesn't change the rows and columns to avoid
1699 // inconsistent states between Vim and MacVim.)
1700 [self queueMessage:msgid data:d];
1702 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1703 gui_resize_shell(cols, rows);
1704 } else if (ExecuteMenuMsgID == msgid) {
1705 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1707 NSArray *desc = [attrs objectForKey:@"descriptor"];
1708 vimmenu_T *menu = menu_for_descriptor(desc);
1712 } else if (ToggleToolbarMsgID == msgid) {
1713 [self handleToggleToolbar];
1714 } else if (ScrollbarEventMsgID == msgid) {
1715 [self handleScrollbarEvent:data];
1716 } else if (SetFontMsgID == msgid) {
1717 [self handleSetFont:data];
1718 } else if (VimShouldCloseMsgID == msgid) {
1720 } else if (DropFilesMsgID == msgid) {
1721 [self handleDropFiles:data];
1722 } else if (DropStringMsgID == msgid) {
1723 [self handleDropString:data];
1724 } else if (GotFocusMsgID == msgid) {
1726 [self focusChange:YES];
1727 } else if (LostFocusMsgID == msgid) {
1729 [self focusChange:NO];
1730 } else if (SetMouseShapeMsgID == msgid) {
1731 const void *bytes = [data bytes];
1732 int shape = *((int*)bytes); bytes += sizeof(int);
1733 update_mouseshape(shape);
1734 } else if (XcodeModMsgID == msgid) {
1735 [self handleXcodeMod:data];
1736 } else if (OpenWithArgumentsMsgID == msgid) {
1737 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1739 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1743 + (NSDictionary *)specialKeys
1745 static NSDictionary *specialKeys = nil;
1748 NSBundle *mainBundle = [NSBundle mainBundle];
1749 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1751 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1757 - (void)handleInsertText:(NSData *)data
1761 NSString *key = [[NSString alloc] initWithData:data
1762 encoding:NSUTF8StringEncoding];
1763 char_u *str = (char_u*)[key UTF8String];
1764 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1767 char_u *conv_str = NULL;
1768 if (input_conv.vc_type != CONV_NONE) {
1769 conv_str = string_convert(&input_conv, str, &len);
1775 for (i = 0; i < len; ++i) {
1776 add_to_input_buf(str+i, 1);
1777 if (CSI == str[i]) {
1778 // NOTE: If the converted string contains the byte CSI, then it
1779 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1781 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1782 add_to_input_buf(extra, 2);
1793 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1797 char_u *chars = (char_u*)[key UTF8String];
1799 char_u *conv_str = NULL;
1801 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1803 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1804 // that new keys can easily be added.
1805 NSString *specialString = [[MMBackend specialKeys]
1807 if (specialString && [specialString length] > 1) {
1808 //NSLog(@"special key: %@", specialString);
1809 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1810 [specialString characterAtIndex:1]);
1812 ikey = simplify_key(ikey, &mods);
1817 special[1] = K_SECOND(ikey);
1818 special[2] = K_THIRD(ikey);
1822 } else if (1 == length && TAB == chars[0]) {
1823 // Tab is a trouble child:
1824 // - <Tab> is added to the input buffer as is
1825 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1826 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1827 // to be converted to utf-8
1828 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1829 // - <C-Tab> is reserved by Mac OS X
1830 // - <D-Tab> is reserved by Mac OS X
1835 if (mods & MOD_MASK_SHIFT) {
1836 mods &= ~MOD_MASK_SHIFT;
1838 special[1] = K_SECOND(K_S_TAB);
1839 special[2] = K_THIRD(K_S_TAB);
1841 } else if (mods & MOD_MASK_ALT) {
1842 int mtab = 0x80 | TAB;
1846 special[0] = (mtab >> 6) + 0xc0;
1847 special[1] = mtab & 0xbf;
1855 mods &= ~MOD_MASK_ALT;
1857 } else if (length > 0) {
1858 unichar c = [key characterAtIndex:0];
1860 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1861 // [key characterAtIndex:0], mods);
1863 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1864 // cleared since they are already added to the key by the AppKit.
1865 // Unfortunately, the only way to deal with when to clear the modifiers
1866 // or not seems to be to have hard-wired rules like this.
1867 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1868 || 0x9 == c || 0xd == c || ESC == c) ) {
1869 mods &= ~MOD_MASK_SHIFT;
1870 mods &= ~MOD_MASK_CTRL;
1871 //NSLog(@"clear shift ctrl");
1874 // HACK! All Option+key presses go via 'insert text' messages, except
1875 // for <M-Space>. If the Alt flag is not cleared for <M-Space> it does
1876 // not work to map to it.
1877 if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1878 //NSLog(@"clear alt");
1879 mods &= ~MOD_MASK_ALT;
1883 if (input_conv.vc_type != CONV_NONE) {
1884 conv_str = string_convert(&input_conv, chars, &length);
1891 if (chars && length > 0) {
1893 //NSLog(@"adding mods: %d", mods);
1895 modChars[1] = KS_MODIFIER;
1897 add_to_input_buf(modChars, 3);
1900 //NSLog(@"add to input buf: 0x%x", chars[0]);
1901 // TODO: Check for CSI bytes?
1902 add_to_input_buf(chars, length);
1911 - (void)queueMessage:(int)msgid data:(NSData *)data
1913 //if (msgid != EnableMenuItemMsgID)
1914 // NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1916 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1918 [outputQueue addObject:data];
1920 [outputQueue addObject:[NSData data]];
1923 - (void)connectionDidDie:(NSNotification *)notification
1925 // If the main connection to MacVim is lost this means that MacVim was
1926 // either quit (by the user chosing Quit on the MacVim menu), or it has
1927 // crashed. In the former case the flag 'isTerminating' is set and we then
1928 // quit cleanly; in the latter case we make sure the swap files are left
1931 // NOTE: This is not called if a Vim controller invalidates its connection.
1933 //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1937 getout_preserve_modified(1);
1940 - (void)blinkTimerFired:(NSTimer *)timer
1942 NSTimeInterval timeInterval = 0;
1944 [blinkTimer release];
1947 if (MMBlinkStateOn == blinkState) {
1948 gui_undraw_cursor();
1949 blinkState = MMBlinkStateOff;
1950 timeInterval = blinkOffInterval;
1951 } else if (MMBlinkStateOff == blinkState) {
1952 gui_update_cursor(TRUE, FALSE);
1953 blinkState = MMBlinkStateOn;
1954 timeInterval = blinkOnInterval;
1957 if (timeInterval > 0) {
1959 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1960 selector:@selector(blinkTimerFired:)
1961 userInfo:nil repeats:NO] retain];
1962 [self flushQueue:YES];
1966 - (void)focusChange:(BOOL)on
1968 gui_focus_change(on);
1971 - (void)handleToggleToolbar
1973 // If 'go' contains 'T', then remove it, else add it.
1975 char_u go[sizeof(GO_ALL)+2];
1980 p = vim_strchr(go, GO_TOOLBAR);
1984 char_u *end = go + len;
1990 go[len] = GO_TOOLBAR;
1994 set_option_value((char_u*)"guioptions", 0, go, 0);
1996 [self redrawScreen];
1999 - (void)handleScrollbarEvent:(NSData *)data
2003 const void *bytes = [data bytes];
2004 long ident = *((long*)bytes); bytes += sizeof(long);
2005 int hitPart = *((int*)bytes); bytes += sizeof(int);
2006 float fval = *((float*)bytes); bytes += sizeof(float);
2007 scrollbar_T *sb = gui_find_scrollbar(ident);
2010 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2011 long value = sb_info->value;
2012 long size = sb_info->size;
2013 long max = sb_info->max;
2014 BOOL isStillDragging = NO;
2015 BOOL updateKnob = YES;
2018 case NSScrollerDecrementPage:
2019 value -= (size > 2 ? size - 2 : 1);
2021 case NSScrollerIncrementPage:
2022 value += (size > 2 ? size - 2 : 1);
2024 case NSScrollerDecrementLine:
2027 case NSScrollerIncrementLine:
2030 case NSScrollerKnob:
2031 isStillDragging = YES;
2033 case NSScrollerKnobSlot:
2034 value = (long)(fval * (max - size + 1));
2041 //NSLog(@"value %d -> %d", sb_info->value, value);
2042 gui_drag_scrollbar(sb, value, isStillDragging);
2045 // Dragging the knob or option+clicking automatically updates
2046 // the knob position (on the actual NSScroller), so we only
2047 // need to set the knob position in the other cases.
2049 // Update both the left&right vertical scrollbars.
2050 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2051 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2052 [self setScrollbarThumbValue:value size:size max:max
2053 identifier:identLeft];
2054 [self setScrollbarThumbValue:value size:size max:max
2055 identifier:identRight];
2057 // Update the horizontal scrollbar.
2058 [self setScrollbarThumbValue:value size:size max:max
2065 - (void)handleSetFont:(NSData *)data
2069 const void *bytes = [data bytes];
2070 float pointSize = *((float*)bytes); bytes += sizeof(float);
2071 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2072 bytes += sizeof(unsigned); // len not used
2074 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2075 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2076 char_u *s = (char_u*)[name UTF8String];
2079 s = CONVERT_FROM_UTF8(s);
2082 set_option_value((char_u*)"guifont", 0, s, 0);
2085 CONVERT_FROM_UTF8_FREE(s);
2088 [self redrawScreen];
2091 - (void)handleDropFiles:(NSData *)data
2093 // TODO: Get rid of this method; instead use Vim script directly. At the
2094 // moment I know how to do this to open files in tabs, but I'm not sure how
2095 // to add the filenames to the command line when in command line mode.
2099 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2102 id obj = [args objectForKey:@"forceOpen"];
2103 BOOL forceOpen = YES;
2105 forceOpen = [obj boolValue];
2107 NSArray *filenames = [args objectForKey:@"filenames"];
2108 if (!(filenames && [filenames count] > 0)) return;
2111 if (!forceOpen && (State & CMDLINE)) {
2112 // HACK! If Vim is in command line mode then the files names
2113 // should be added to the command line, instead of opening the
2114 // files in tabs (unless forceOpen is set). This is taken care of by
2115 // gui_handle_drop().
2116 int n = [filenames count];
2117 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2120 for (i = 0; i < n; ++i)
2121 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2123 // NOTE! This function will free 'fnames'.
2124 // HACK! It is assumed that the 'x' and 'y' arguments are
2125 // unused when in command line mode.
2126 gui_handle_drop(0, 0, 0, fnames, n);
2131 [self handleOpenWithArguments:args];
2135 - (void)handleDropString:(NSData *)data
2140 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2141 const void *bytes = [data bytes];
2142 int len = *((int*)bytes); bytes += sizeof(int);
2143 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2145 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2146 NSRange range = { 0, [string length] };
2147 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2148 withString:@"\x0a" options:0
2151 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2152 options:0 range:range];
2155 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2156 char_u *s = (char_u*)[string UTF8String];
2158 if (input_conv.vc_type != CONV_NONE)
2159 s = string_convert(&input_conv, s, &len);
2161 dnd_yank_drag_data(s, len);
2163 if (input_conv.vc_type != CONV_NONE)
2166 add_to_input_buf(dropkey, sizeof(dropkey));
2170 - (void)startOdbEditWithArguments:(NSDictionary *)args
2172 #ifdef FEAT_ODB_EDITOR
2173 id obj = [args objectForKey:@"remoteID"];
2176 OSType serverID = [obj unsignedIntValue];
2177 NSString *remotePath = [args objectForKey:@"remotePath"];
2179 NSAppleEventDescriptor *token = nil;
2180 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2181 obj = [args objectForKey:@"remoteTokenDescType"];
2182 if (tokenData && obj) {
2183 DescType tokenType = [obj unsignedLongValue];
2184 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2188 NSArray *filenames = [args objectForKey:@"filenames"];
2189 unsigned i, numFiles = [filenames count];
2190 for (i = 0; i < numFiles; ++i) {
2191 NSString *filename = [filenames objectAtIndex:i];
2192 char_u *s = [filename vimStringSave];
2193 buf_T *buf = buflist_findname(s);
2197 if (buf->b_odb_token) {
2198 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2199 buf->b_odb_token = NULL;
2202 if (buf->b_odb_fname) {
2203 vim_free(buf->b_odb_fname);
2204 buf->b_odb_fname = NULL;
2207 buf->b_odb_server_id = serverID;
2210 buf->b_odb_token = [token retain];
2212 buf->b_odb_fname = [remotePath vimStringSave];
2214 NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2218 #endif // FEAT_ODB_EDITOR
2221 - (void)handleXcodeMod:(NSData *)data
2224 const void *bytes = [data bytes];
2225 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2226 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2230 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2231 descriptorWithDescriptorType:type
2237 - (void)handleOpenWithArguments:(NSDictionary *)args
2239 // ARGUMENT: DESCRIPTION:
2240 // -------------------------------------------------------------
2241 // filenames list of filenames
2242 // dontOpen don't open files specified in above argument
2243 // layout which layout to use to open files
2244 // selectionRange range of lines to select
2245 // searchText string to search for
2246 // cursorLine line to position the cursor on
2247 // cursorColumn column to position the cursor on
2248 // (only valid when "cursorLine" is set)
2249 // remoteID ODB parameter
2250 // remotePath ODB parameter
2251 // remoteTokenDescType ODB parameter
2252 // remoteTokenData ODB parameter
2254 //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2256 NSArray *filenames = [args objectForKey:@"filenames"];
2257 int i, numFiles = filenames ? [filenames count] : 0;
2258 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2259 int layout = [[args objectForKey:@"layout"] intValue];
2261 // Change to directory of first file to open if this is an "unused" editor
2262 // (but do not do this if editing remotely).
2263 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2264 && (starting || [self unusedEditor]) ) {
2265 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2271 // When Vim is starting we simply add the files to be opened to the
2272 // global arglist and Vim will take care of opening them for us.
2273 if (openFiles && numFiles > 0) {
2274 for (i = 0; i < numFiles; i++) {
2275 NSString *fname = [filenames objectAtIndex:i];
2278 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2279 || (p = [fname vimStringSave]) == NULL)
2280 exit(2); // See comment in -[MMBackend exit]
2282 alist_add(&global_alist, p, 2);
2285 // Vim will take care of arranging the files added to the arglist
2286 // in windows or tabs; all we must do is to specify which layout to
2288 initialWindowLayout = layout;
2291 // When Vim is already open we resort to some trickery to open the
2292 // files with the specified layout.
2294 // TODO: Figure out a better way to handle this?
2295 if (openFiles && numFiles > 0) {
2296 BOOL oneWindowInTab = topframe ? YES
2297 : (topframe->fr_layout == FR_LEAF);
2298 BOOL bufChanged = NO;
2299 BOOL bufHasFilename = NO;
2301 bufChanged = curbufIsChanged();
2302 bufHasFilename = curbuf->b_ffname != NULL;
2305 // Temporarily disable flushing since the following code may
2306 // potentially cause multiple redraws.
2307 flushDisabled = YES;
2309 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2310 if (WIN_TABS == layout && !onlyOneTab) {
2311 // By going to the last tabpage we ensure that the new tabs
2312 // will appear last (if this call is left out, the taborder
2317 // Make sure we're in normal mode first.
2318 [self addInput:@"<C-\\><C-N>"];
2321 // With "split layout" we open a new tab before opening
2322 // multiple files if the current tab has more than one window
2323 // or if there is exactly one window but whose buffer has a
2324 // filename. (The :drop command ensures modified buffers get
2325 // their own window.)
2326 if ((WIN_HOR == layout || WIN_VER == layout) &&
2327 (!oneWindowInTab || bufHasFilename))
2328 [self addInput:@":tabnew<CR>"];
2330 // The files are opened by constructing a ":drop ..." command
2331 // and executing it.
2332 NSMutableString *cmd = (WIN_TABS == layout)
2333 ? [NSMutableString stringWithString:@":tab drop"]
2334 : [NSMutableString stringWithString:@":drop"];
2336 for (i = 0; i < numFiles; ++i) {
2337 NSString *file = [filenames objectAtIndex:i];
2338 file = [file stringByEscapingSpecialFilenameCharacters];
2339 [cmd appendString:@" "];
2340 [cmd appendString:file];
2343 // Temporarily clear 'suffixes' so that the files are opened in
2344 // the same order as they appear in the "filenames" array.
2345 [self addInput:@":let t:mvim_oldsu=&su|set su=<CR>"];
2347 [self addInput:cmd];
2349 // Split the view into multiple windows if requested.
2350 if (WIN_HOR == layout)
2351 [self addInput:@"|sall"];
2352 else if (WIN_VER == layout)
2353 [self addInput:@"|vert sall"];
2355 // Restore the old value of 'suffixes'.
2356 [self addInput:@"|let &su=t:mvim_oldsu|unlet t:mvim_oldsu"];
2358 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2359 [self addInput:@"|redr|f<CR>"];
2361 // When opening one file we try to reuse the current window,
2362 // but not if its buffer is modified or has a filename.
2363 // However, the 'arglist' layout always opens the file in the
2365 NSString *file = [[filenames lastObject]
2366 stringByEscapingSpecialFilenameCharacters];
2368 if (WIN_HOR == layout) {
2369 if (!(bufHasFilename || bufChanged))
2370 cmd = [NSString stringWithFormat:@":e %@", file];
2372 cmd = [NSString stringWithFormat:@":sp %@", file];
2373 } else if (WIN_VER == layout) {
2374 if (!(bufHasFilename || bufChanged))
2375 cmd = [NSString stringWithFormat:@":e %@", file];
2377 cmd = [NSString stringWithFormat:@":vsp %@", file];
2378 } else if (WIN_TABS == layout) {
2379 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2380 cmd = [NSString stringWithFormat:@":e %@", file];
2382 cmd = [NSString stringWithFormat:@":tabe %@", file];
2384 // (The :drop command will split if there is a modified
2386 cmd = [NSString stringWithFormat:@":drop %@", file];
2389 [self addInput:cmd];
2391 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2392 [self addInput:@"|redr|f<CR>"];
2395 // Force screen redraw (does it have to be this complicated?).
2396 // (This code was taken from the end of gui_handle_drop().)
2397 update_screen(NOT_VALID);
2400 gui_update_cursor(FALSE, FALSE);
2407 if ([args objectForKey:@"remoteID"]) {
2408 // NOTE: We have to delay processing any ODB related arguments since
2409 // the file(s) may not be opened until the input buffer is processed.
2410 [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2415 NSString *lineString = [args objectForKey:@"cursorLine"];
2416 if (lineString && [lineString intValue] > 0) {
2417 NSString *columnString = [args objectForKey:@"cursorColumn"];
2418 if (!(columnString && [columnString intValue] > 0))
2419 columnString = @"1";
2421 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2422 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2423 [self addInput:cmd];
2426 NSString *rangeString = [args objectForKey:@"selectionRange"];
2428 // Build a command line string that will select the given range of
2429 // lines. If range.length == 0, then position the cursor on the given
2430 // line but do not select.
2431 NSRange range = NSRangeFromString(rangeString);
2433 if (range.length > 0) {
2434 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2435 NSMaxRange(range), range.location];
2437 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2441 [self addInput:cmd];
2444 NSString *searchText = [args objectForKey:@"searchText"];
2446 // TODO: Searching is an exclusive motion, so if the pattern would
2447 // match on row 0 column 0 then this pattern will miss that match.
2448 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@<CR>",
2453 - (BOOL)checkForModifiedBuffers
2456 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2457 if (bufIsChanged(buf)) {
2465 - (void)addInput:(NSString *)input
2467 char_u *s = (char_u*)[input UTF8String];
2470 s = CONVERT_FROM_UTF8(s);
2473 server_to_input_buf(s);
2476 CONVERT_FROM_UTF8_FREE(s);
2480 - (BOOL)unusedEditor
2482 BOOL oneWindowInTab = topframe ? YES
2483 : (topframe->fr_layout == FR_LEAF);
2484 BOOL bufChanged = NO;
2485 BOOL bufHasFilename = NO;
2487 bufChanged = curbufIsChanged();
2488 bufHasFilename = curbuf->b_ffname != NULL;
2491 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2493 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2496 - (void)redrawScreen
2498 // Force screen redraw (does it have to be this complicated?).
2499 redraw_all_later(CLEAR);
2500 update_screen(NOT_VALID);
2503 gui_update_cursor(FALSE, FALSE);
2505 // HACK! The cursor is not put back at the command line by the above
2506 // "redraw commands". The following test seems to do the trick though.
2507 if (State & CMDLINE)
2511 @end // MMBackend (Private)
2516 @implementation MMBackend (ClientServer)
2518 - (NSString *)connectionNameFromServerName:(NSString *)name
2520 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2522 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2526 - (NSConnection *)connectionForServerName:(NSString *)name
2528 // TODO: Try 'name%d' if 'name' fails.
2529 NSString *connName = [self connectionNameFromServerName:name];
2530 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2533 svrConn = [NSConnection connectionWithRegisteredName:connName
2535 // Try alternate server...
2536 if (!svrConn && alternateServerName) {
2537 //NSLog(@" trying to connect to alternate server: %@",
2538 // alternateServerName);
2539 connName = [self connectionNameFromServerName:alternateServerName];
2540 svrConn = [NSConnection connectionWithRegisteredName:connName
2544 // Try looking for alternate servers...
2546 //NSLog(@" looking for alternate servers...");
2547 NSString *alt = [self alternateServerNameForName:name];
2548 if (alt != alternateServerName) {
2549 //NSLog(@" found alternate server: %@", string);
2550 [alternateServerName release];
2551 alternateServerName = [alt copy];
2555 // Try alternate server again...
2556 if (!svrConn && alternateServerName) {
2557 //NSLog(@" trying to connect to alternate server: %@",
2558 // alternateServerName);
2559 connName = [self connectionNameFromServerName:alternateServerName];
2560 svrConn = [NSConnection connectionWithRegisteredName:connName
2565 [connectionNameDict setObject:svrConn forKey:connName];
2567 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2568 [[NSNotificationCenter defaultCenter] addObserver:self
2569 selector:@selector(serverConnectionDidDie:)
2570 name:NSConnectionDidDieNotification object:svrConn];
2577 - (NSConnection *)connectionForServerPort:(int)port
2580 NSEnumerator *e = [connectionNameDict objectEnumerator];
2582 while ((conn = [e nextObject])) {
2583 // HACK! Assume connection uses mach ports.
2584 if (port == [(NSMachPort*)[conn sendPort] machPort])
2591 - (void)serverConnectionDidDie:(NSNotification *)notification
2593 //NSLog(@"%s%@", _cmd, notification);
2595 NSConnection *svrConn = [notification object];
2597 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2598 [[NSNotificationCenter defaultCenter]
2600 name:NSConnectionDidDieNotification
2603 [connectionNameDict removeObjectsForKeys:
2604 [connectionNameDict allKeysForObject:svrConn]];
2606 // HACK! Assume connection uses mach ports.
2607 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2608 NSNumber *key = [NSNumber numberWithInt:port];
2610 [clientProxyDict removeObjectForKey:key];
2611 [serverReplyDict removeObjectForKey:key];
2614 - (void)addClient:(NSDistantObject *)client
2616 NSConnection *conn = [client connectionForProxy];
2617 // HACK! Assume connection uses mach ports.
2618 int port = [(NSMachPort*)[conn sendPort] machPort];
2619 NSNumber *key = [NSNumber numberWithInt:port];
2621 if (![clientProxyDict objectForKey:key]) {
2622 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2623 [clientProxyDict setObject:client forKey:key];
2626 // NOTE: 'clientWindow' is a global variable which is used by <client>
2627 clientWindow = port;
2630 - (NSString *)alternateServerNameForName:(NSString *)name
2632 if (!(name && [name length] > 0))
2635 // Only look for alternates if 'name' doesn't end in a digit.
2636 unichar lastChar = [name characterAtIndex:[name length]-1];
2637 if (lastChar >= '0' && lastChar <= '9')
2640 // Look for alternates among all current servers.
2641 NSArray *list = [self serverList];
2642 if (!(list && [list count] > 0))
2645 // Filter out servers starting with 'name' and ending with a number. The
2646 // (?i) pattern ensures that the match is case insensitive.
2647 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2648 NSPredicate *pred = [NSPredicate predicateWithFormat:
2649 @"SELF MATCHES %@", pat];
2650 list = [list filteredArrayUsingPredicate:pred];
2651 if ([list count] > 0) {
2652 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2653 return [list objectAtIndex:0];
2659 @end // MMBackend (ClientServer)
2664 @implementation NSString (MMServerNameCompare)
2665 - (NSComparisonResult)serverNameCompare:(NSString *)string
2667 return [self compare:string
2668 options:NSCaseInsensitiveSearch|NSNumericSearch];
2675 static int eventModifierFlagsToVimModMask(int modifierFlags)
2679 if (modifierFlags & NSShiftKeyMask)
2680 modMask |= MOD_MASK_SHIFT;
2681 if (modifierFlags & NSControlKeyMask)
2682 modMask |= MOD_MASK_CTRL;
2683 if (modifierFlags & NSAlternateKeyMask)
2684 modMask |= MOD_MASK_ALT;
2685 if (modifierFlags & NSCommandKeyMask)
2686 modMask |= MOD_MASK_CMD;
2691 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2695 if (modifierFlags & NSShiftKeyMask)
2696 modMask |= MOUSE_SHIFT;
2697 if (modifierFlags & NSControlKeyMask)
2698 modMask |= MOUSE_CTRL;
2699 if (modifierFlags & NSAlternateKeyMask)
2700 modMask |= MOUSE_ALT;
2705 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2707 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2709 return (buttonNumber >= 0 && buttonNumber < 3)
2710 ? mouseButton[buttonNumber] : -1;
2715 // This function is modeled after the VimToPython function found in if_python.c
2716 // NB This does a deep copy by value, it does not lookup references like the
2717 // VimToPython function does. This is because I didn't want to deal with the
2718 // retain cycles that this would create, and we can cover 99% of the use cases
2719 // by ignoring it. If we ever switch to using GC in MacVim then this
2720 // functionality can be implemented easily.
2721 static id vimToCocoa(typval_T * tv, int depth)
2727 // Avoid infinite recursion
2732 if (tv->v_type == VAR_STRING) {
2733 char_u * val = tv->vval.v_string;
2734 // val can be NULL if the string is empty
2736 result = [NSString string];
2739 val = CONVERT_TO_UTF8(val);
2741 result = [NSString stringWithUTF8String:(char*)val];
2743 CONVERT_TO_UTF8_FREE(val);
2746 } else if (tv->v_type == VAR_NUMBER) {
2747 // looks like sizeof(varnumber_T) is always <= sizeof(long)
2748 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2749 } else if (tv->v_type == VAR_LIST) {
2750 list_T * list = tv->vval.v_list;
2753 NSMutableArray * arr = result = [NSMutableArray array];
2756 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2757 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2758 [arr addObject:newObj];
2761 } else if (tv->v_type == VAR_DICT) {
2762 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2764 if (tv->vval.v_dict != NULL) {
2765 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2766 int todo = ht->ht_used;
2770 for (hi = ht->ht_array; todo > 0; ++hi) {
2771 if (!HASHITEM_EMPTY(hi)) {
2774 di = dict_lookup(hi);
2775 newObj = vimToCocoa(&di->di_tv, depth + 1);
2777 char_u * keyval = hi->hi_key;
2779 keyval = CONVERT_TO_UTF8(keyval);
2781 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2783 CONVERT_TO_UTF8_FREE(keyval);
2785 [dict setObject:newObj forKey:key];
2789 } else { // only func refs should fall into this category?
2797 // This function is modeled after eval_client_expr_to_string found in main.c
2798 // Returns nil if there was an error evaluating the expression, and writes a
2799 // message to errorStr.
2800 // TODO Get the error that occurred while evaluating the expression in vim
2802 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2805 char_u *s = (char_u*)[expr UTF8String];
2808 s = CONVERT_FROM_UTF8(s);
2811 int save_dbl = debug_break_level;
2812 int save_ro = redir_off;
2814 debug_break_level = -1;
2818 typval_T * tvres = eval_expr(s, NULL);
2820 debug_break_level = save_dbl;
2821 redir_off = save_ro;
2828 CONVERT_FROM_UTF8_FREE(s);
2833 gui_update_cursor(FALSE, FALSE);
2836 if (tvres == NULL) {
2838 *errstr = @"Expression evaluation failed.";
2841 id res = vimToCocoa(tvres, 1);
2846 *errstr = @"Conversion to cocoa values failed.";
2854 @implementation NSString (VimStrings)
2856 + (id)stringWithVimString:(char_u *)s
2858 // This method ensures a non-nil string is returned. If 's' cannot be
2859 // converted to a utf-8 string it is assumed to be latin-1. If conversion
2860 // still fails an empty NSString is returned.
2861 NSString *string = nil;
2864 s = CONVERT_TO_UTF8(s);
2866 string = [NSString stringWithUTF8String:(char*)s];
2868 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2870 string = [NSString stringWithCString:(char*)s
2871 encoding:NSISOLatin1StringEncoding];
2874 CONVERT_TO_UTF8_FREE(s);
2878 return string != nil ? string : [NSString string];
2881 - (char_u *)vimStringSave
2883 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2886 s = CONVERT_FROM_UTF8(s);
2888 ret = vim_strsave(s);
2890 CONVERT_FROM_UTF8_FREE(s);
2896 @end // NSString (VimStrings)