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);
72 static NSString *MMSymlinkWarningString =
73 @"\n\n\tMost likely this is because you have symlinked directly to\n"
74 "\tthe Vim binary, which Cocoa does not allow. Please use an\n"
75 "\talias or the mvim shell script instead. If you have not used\n"
76 "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
80 @interface NSString (MMServerNameCompare)
81 - (NSComparisonResult)serverNameCompare:(NSString *)string;
87 @interface MMBackend (Private)
88 - (void)waitForDialogReturn;
89 - (void)insertVimStateMessage;
90 - (void)processInputQueue;
91 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
92 + (NSDictionary *)specialKeys;
93 - (void)handleInsertText:(NSData *)data;
94 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
95 - (void)queueMessage:(int)msgid data:(NSData *)data;
96 - (void)connectionDidDie:(NSNotification *)notification;
97 - (void)blinkTimerFired:(NSTimer *)timer;
98 - (void)focusChange:(BOOL)on;
99 - (void)handleToggleToolbar;
100 - (void)handleScrollbarEvent:(NSData *)data;
101 - (void)handleSetFont:(NSData *)data;
102 - (void)handleDropFiles:(NSData *)data;
103 - (void)handleDropString:(NSData *)data;
104 - (void)startOdbEditWithArguments:(NSDictionary *)args;
105 - (void)handleXcodeMod:(NSData *)data;
106 - (void)handleOpenWithArguments:(NSDictionary *)args;
107 - (BOOL)checkForModifiedBuffers;
108 - (void)addInput:(NSString *)input;
109 - (BOOL)unusedEditor;
114 @interface MMBackend (ClientServer)
115 - (NSString *)connectionNameFromServerName:(NSString *)name;
116 - (NSConnection *)connectionForServerName:(NSString *)name;
117 - (NSConnection *)connectionForServerPort:(int)port;
118 - (void)serverConnectionDidDie:(NSNotification *)notification;
119 - (void)addClient:(NSDistantObject *)client;
120 - (NSString *)alternateServerNameForName:(NSString *)name;
125 @implementation MMBackend
127 + (MMBackend *)sharedInstance
129 static MMBackend *singleton = nil;
130 return singleton ? singleton : (singleton = [MMBackend new]);
136 if (!self) return nil;
138 fontContainerRef = loadFonts();
140 outputQueue = [[NSMutableArray alloc] init];
141 inputQueue = [[NSMutableArray alloc] init];
142 drawData = [[NSMutableData alloc] initWithCapacity:1024];
143 connectionNameDict = [[NSMutableDictionary alloc] init];
144 clientProxyDict = [[NSMutableDictionary alloc] init];
145 serverReplyDict = [[NSMutableDictionary alloc] init];
147 NSBundle *mainBundle = [NSBundle mainBundle];
148 NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
150 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
152 path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
154 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
157 path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
159 actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
161 if (!(colorDict && sysColorDict && actionDict))
162 NSLog(@"ERROR: Failed to load dictionaries.%@",
163 MMSymlinkWarningString);
170 //NSLog(@"%@ %s", [self className], _cmd);
171 [[NSNotificationCenter defaultCenter] removeObserver:self];
173 [oldWideFont release]; oldWideFont = nil;
174 [blinkTimer release]; blinkTimer = nil;
175 [alternateServerName release]; alternateServerName = nil;
176 [serverReplyDict release]; serverReplyDict = nil;
177 [clientProxyDict release]; clientProxyDict = nil;
178 [connectionNameDict release]; connectionNameDict = nil;
179 [inputQueue release]; inputQueue = nil;
180 [outputQueue release]; outputQueue = nil;
181 [drawData release]; drawData = nil;
182 [frontendProxy release]; frontendProxy = nil;
183 [connection release]; connection = nil;
184 [actionDict release]; actionDict = nil;
185 [sysColorDict release]; sysColorDict = nil;
186 [colorDict release]; colorDict = nil;
191 - (void)setBackgroundColor:(int)color
193 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
196 - (void)setForegroundColor:(int)color
198 foregroundColor = MM_COLOR(color);
201 - (void)setSpecialColor:(int)color
203 specialColor = MM_COLOR(color);
206 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
208 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
209 defaultForegroundColor = MM_COLOR(fg);
211 NSMutableData *data = [NSMutableData data];
213 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
214 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
216 [self queueMessage:SetDefaultColorsMsgID data:data];
219 - (NSConnection *)connection
222 // NOTE! If the name of the connection changes here it must also be
223 // updated in MMAppController.m.
224 NSString *name = [NSString stringWithFormat:@"%@-connection",
225 [[NSBundle mainBundle] bundlePath]];
227 connection = [NSConnection connectionWithRegisteredName:name host:nil];
231 // NOTE: 'connection' may be nil here.
235 - (NSDictionary *)actionDict
240 - (int)initialWindowLayout
242 return initialWindowLayout;
245 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
247 [self queueMessage:msgid data:[props dictionaryAsData]];
252 if (![self connection]) {
254 // This is a preloaded process and as such should not cause the
255 // MacVim to be opened. We probably got here as a result of the
256 // user quitting MacVim while the process was preloading, so exit
258 // (Don't use mch_exit() since it assumes the process has properly
263 NSBundle *mainBundle = [NSBundle mainBundle];
268 // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
269 // the API to pass Apple Event parameters is broken on 10.4).
270 NSString *path = [mainBundle bundlePath];
271 status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
272 if (noErr == status) {
273 // Pass parameter to the 'Open' Apple Event that tells MacVim not
274 // to open an untitled window.
275 NSAppleEventDescriptor *desc =
276 [NSAppleEventDescriptor recordDescriptor];
277 [desc setParamDescriptor:
278 [NSAppleEventDescriptor descriptorWithBoolean:NO]
279 forKeyword:keyMMUntitledWindow];
281 LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
282 kLSLaunchDefaults, NULL };
283 status = LSOpenFromRefSpec(&spec, NULL);
286 if (noErr != status) {
287 NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
288 path, MMSymlinkWarningString);
292 // Launch MacVim using NSTask. For some reason the above code using
293 // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
294 // fails, the dock icon starts bouncing and never stops). It seems
295 // like rebuilding the Launch Services database takes care of this
296 // problem, but the NSTask way seems more stable so stick with it.
298 // NOTE! Using NSTask to launch the GUI has the negative side-effect
299 // that the GUI won't be activated (or raised) so there is a hack in
300 // MMAppController which raises the app when a new window is opened.
301 NSMutableArray *args = [NSMutableArray arrayWithObjects:
302 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
303 NSString *exeName = [[mainBundle infoDictionary]
304 objectForKey:@"CFBundleExecutable"];
305 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
307 NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
308 MMSymlinkWarningString);
312 [NSTask launchedTaskWithLaunchPath:path arguments:args];
315 // HACK! Poll the mach bootstrap server until it returns a valid
316 // connection to detect that MacVim has finished launching. Also set a
317 // time-out date so that we don't get stuck doing this forever.
318 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
319 while (![self connection] &&
320 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
321 [[NSRunLoop currentRunLoop]
322 runMode:NSDefaultRunLoopMode
323 beforeDate:[NSDate dateWithTimeIntervalSinceNow:1]];
325 // NOTE: [self connection] will set 'connection' as a side-effect.
327 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
334 [[NSNotificationCenter defaultCenter] addObserver:self
335 selector:@selector(connectionDidDie:)
336 name:NSConnectionDidDieNotification object:connection];
338 id proxy = [connection rootProxy];
339 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
341 int pid = [[NSProcessInfo processInfo] processIdentifier];
343 frontendProxy = [proxy connectBackend:self pid:pid];
345 [frontendProxy retain];
346 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
350 @catch (NSException *e) {
351 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
357 - (BOOL)openGUIWindow
359 [self queueMessage:OpenWindowMsgID data:nil];
365 int type = ClearAllDrawType;
367 // Any draw commands in queue are effectively obsolete since this clearAll
368 // will negate any effect they have, therefore we may as well clear the
370 [drawData setLength:0];
372 [drawData appendBytes:&type length:sizeof(int)];
375 - (void)clearBlockFromRow:(int)row1 column:(int)col1
376 toRow:(int)row2 column:(int)col2
378 int type = ClearBlockDrawType;
380 [drawData appendBytes:&type length:sizeof(int)];
382 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
383 [drawData appendBytes:&row1 length:sizeof(int)];
384 [drawData appendBytes:&col1 length:sizeof(int)];
385 [drawData appendBytes:&row2 length:sizeof(int)];
386 [drawData appendBytes:&col2 length:sizeof(int)];
389 - (void)deleteLinesFromRow:(int)row count:(int)count
390 scrollBottom:(int)bottom left:(int)left right:(int)right
392 int type = DeleteLinesDrawType;
394 [drawData appendBytes:&type length:sizeof(int)];
396 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
397 [drawData appendBytes:&row length:sizeof(int)];
398 [drawData appendBytes:&count length:sizeof(int)];
399 [drawData appendBytes:&bottom length:sizeof(int)];
400 [drawData appendBytes:&left length:sizeof(int)];
401 [drawData appendBytes:&right length:sizeof(int)];
404 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
405 cells:(int)cells flags:(int)flags
407 if (len <= 0 || cells <= 0) return;
409 int type = DrawStringDrawType;
411 [drawData appendBytes:&type length:sizeof(int)];
413 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
414 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
415 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
416 [drawData appendBytes:&row length:sizeof(int)];
417 [drawData appendBytes:&col length:sizeof(int)];
418 [drawData appendBytes:&cells length:sizeof(int)];
419 [drawData appendBytes:&flags length:sizeof(int)];
420 [drawData appendBytes:&len length:sizeof(int)];
421 [drawData appendBytes:s length:len];
424 - (void)insertLinesFromRow:(int)row count:(int)count
425 scrollBottom:(int)bottom left:(int)left right:(int)right
427 int type = InsertLinesDrawType;
429 [drawData appendBytes:&type length:sizeof(int)];
431 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
432 [drawData appendBytes:&row length:sizeof(int)];
433 [drawData appendBytes:&count length:sizeof(int)];
434 [drawData appendBytes:&bottom length:sizeof(int)];
435 [drawData appendBytes:&left length:sizeof(int)];
436 [drawData appendBytes:&right length:sizeof(int)];
439 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
440 fraction:(int)percent color:(int)color
442 int type = DrawCursorDrawType;
443 unsigned uc = MM_COLOR(color);
445 [drawData appendBytes:&type length:sizeof(int)];
447 [drawData appendBytes:&uc length:sizeof(unsigned)];
448 [drawData appendBytes:&row length:sizeof(int)];
449 [drawData appendBytes:&col length:sizeof(int)];
450 [drawData appendBytes:&shape length:sizeof(int)];
451 [drawData appendBytes:&percent length:sizeof(int)];
454 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
455 numColumns:(int)nc invert:(int)invert
457 int type = DrawInvertedRectDrawType;
458 [drawData appendBytes:&type length:sizeof(int)];
460 [drawData appendBytes:&row length:sizeof(int)];
461 [drawData appendBytes:&col length:sizeof(int)];
462 [drawData appendBytes:&nr length:sizeof(int)];
463 [drawData appendBytes:&nc length:sizeof(int)];
464 [drawData appendBytes:&invert length:sizeof(int)];
469 // Tend to the run loop, returning immediately if there are no events
471 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
472 beforeDate:[NSDate distantPast]];
475 - (void)flushQueue:(BOOL)force
477 // NOTE: This variable allows for better control over when the queue is
478 // flushed. It can be set to YES at the beginning of a sequence of calls
479 // that may potentially add items to the queue, and then restored back to
481 if (flushDisabled) return;
483 if ([drawData length] > 0) {
484 // HACK! Detect changes to 'guifontwide'.
485 if (gui.wide_font != (GuiFont)oldWideFont) {
486 [oldWideFont release];
487 oldWideFont = [(NSFont*)gui.wide_font retain];
488 [self setWideFont:oldWideFont];
491 int type = SetCursorPosDrawType;
492 [drawData appendBytes:&type length:sizeof(type)];
493 [drawData appendBytes:&gui.row length:sizeof(gui.row)];
494 [drawData appendBytes:&gui.col length:sizeof(gui.col)];
496 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
497 [drawData setLength:0];
500 if ([outputQueue count] > 0) {
501 [self insertVimStateMessage];
504 [frontendProxy processCommandQueue:outputQueue];
506 @catch (NSException *e) {
507 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
510 [outputQueue removeAllObjects];
514 - (BOOL)waitForInput:(int)milliseconds
516 // Return NO if we timed out waiting for input, otherwise return YES.
517 BOOL inputReceived = NO;
519 // Only start the run loop if the input queue is empty, otherwise process
520 // the input first so that the input on queue isn't delayed.
521 if ([inputQueue count]) {
524 // Wait for the specified amount of time, unless 'milliseconds' is
525 // negative in which case we wait "forever" (1e6 seconds translates to
526 // approximately 11 days).
527 CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
529 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
530 == kCFRunLoopRunHandledSource) {
531 // In order to ensure that all input on the run-loop has been
532 // processed we set the timeout to 0 and keep processing until the
533 // run-loop times out.
539 // The above calls may have placed messages on the input queue so process
540 // it now. This call may enter a blocking loop.
541 if ([inputQueue count] > 0)
542 [self processInputQueue];
544 return inputReceived;
549 // NOTE: This is called if mch_exit() is called. Since we assume here that
550 // the process has started properly, be sure to use exit() instead of
551 // mch_exit() to prematurely terminate a process.
553 // To notify MacVim that this Vim process is exiting we could simply
554 // invalidate the connection and it would automatically receive a
555 // connectionDidDie: notification. However, this notification seems to
556 // take up to 300 ms to arrive which is quite a noticeable delay. Instead
557 // we immediately send a message to MacVim asking it to close the window
558 // belonging to this process, and then we invalidate the connection (in
559 // case the message got lost).
561 // Make sure no connectionDidDie: notification is received now that we are
563 [[NSNotificationCenter defaultCenter] removeObserver:self];
565 if ([connection isValid]) {
567 // Flush the entire queue in case a VimLeave autocommand added
568 // something to the queue.
569 [self queueMessage:CloseWindowMsgID data:nil];
570 [frontendProxy processCommandQueue:outputQueue];
572 @catch (NSException *e) {
573 NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
576 [connection invalidate];
579 #ifdef MAC_CLIENTSERVER
580 // The default connection is used for the client/server code.
581 [[NSConnection defaultConnection] setRootObject:nil];
582 [[NSConnection defaultConnection] invalidate];
585 if (fontContainerRef) {
586 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
587 fontContainerRef = 0;
590 usleep(MMExitProcessDelay);
593 - (void)selectTab:(int)index
595 //NSLog(@"%s%d", _cmd, index);
598 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
599 [self queueMessage:SelectTabMsgID data:data];
604 //NSLog(@"%s", _cmd);
606 NSMutableData *data = [NSMutableData data];
608 int idx = tabpage_index(curtab) - 1;
609 [data appendBytes:&idx length:sizeof(int)];
612 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
613 // This function puts the label of the tab in the global 'NameBuff'.
614 get_tabline_label(tp, FALSE);
615 char_u *s = NameBuff;
617 if (len <= 0) continue;
620 s = CONVERT_TO_UTF8(s);
623 // Count the number of windows in the tabpage.
624 //win_T *wp = tp->tp_firstwin;
626 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
628 //[data appendBytes:&wincount length:sizeof(int)];
629 [data appendBytes:&len length:sizeof(int)];
630 [data appendBytes:s length:len];
633 CONVERT_TO_UTF8_FREE(s);
637 [self queueMessage:UpdateTabBarMsgID data:data];
640 - (BOOL)tabBarVisible
642 return tabBarVisible;
645 - (void)showTabBar:(BOOL)enable
647 tabBarVisible = enable;
649 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
650 [self queueMessage:msgid data:nil];
653 - (void)setRows:(int)rows columns:(int)cols
655 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
657 int dim[] = { rows, cols };
658 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
660 [self queueMessage:SetTextDimensionsMsgID data:data];
663 - (void)setWindowTitle:(char *)title
665 NSMutableData *data = [NSMutableData data];
666 int len = strlen(title);
667 if (len <= 0) return;
669 [data appendBytes:&len length:sizeof(int)];
670 [data appendBytes:title length:len];
672 [self queueMessage:SetWindowTitleMsgID data:data];
675 - (void)setDocumentFilename:(char *)filename
677 NSMutableData *data = [NSMutableData data];
678 int len = filename ? strlen(filename) : 0;
680 [data appendBytes:&len length:sizeof(int)];
682 [data appendBytes:filename length:len];
684 [self queueMessage:SetDocumentFilenameMsgID data:data];
687 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
692 [frontendProxy showSavePanelWithAttributes:attr];
694 [self waitForDialogReturn];
696 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]]) {
697 char_u *ret = (char_u*)[dialogReturn UTF8String];
699 ret = CONVERT_FROM_UTF8(ret);
701 s = vim_strsave(ret);
703 CONVERT_FROM_UTF8_FREE(ret);
707 [dialogReturn release]; dialogReturn = nil;
709 @catch (NSException *e) {
710 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
716 - (oneway void)setDialogReturn:(in bycopy id)obj
718 // NOTE: This is called by
719 // - [MMVimController panelDidEnd:::], and
720 // - [MMVimController alertDidEnd:::],
721 // to indicate that a save/open panel or alert has finished.
723 // We want to distinguish between "no dialog return yet" and "dialog
724 // returned nothing". The former can be tested with dialogReturn == nil,
725 // the latter with dialogReturn == [NSNull null].
726 if (!obj) obj = [NSNull null];
728 if (obj != dialogReturn) {
729 [dialogReturn release];
730 dialogReturn = [obj retain];
734 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
739 [frontendProxy presentDialogWithAttributes:attr];
741 [self waitForDialogReturn];
743 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
744 && [dialogReturn count]) {
745 retval = [[dialogReturn objectAtIndex:0] intValue];
746 if (txtfield && [dialogReturn count] > 1) {
747 NSString *retString = [dialogReturn objectAtIndex:1];
748 char_u *ret = (char_u*)[retString UTF8String];
750 ret = CONVERT_FROM_UTF8(ret);
752 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
754 CONVERT_FROM_UTF8_FREE(ret);
759 [dialogReturn release]; dialogReturn = nil;
761 @catch (NSException *e) {
762 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
768 - (void)showToolbar:(int)enable flags:(int)flags
770 NSMutableData *data = [NSMutableData data];
772 [data appendBytes:&enable length:sizeof(int)];
773 [data appendBytes:&flags length:sizeof(int)];
775 [self queueMessage:ShowToolbarMsgID data:data];
778 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
780 NSMutableData *data = [NSMutableData data];
782 [data appendBytes:&ident length:sizeof(long)];
783 [data appendBytes:&type length:sizeof(int)];
785 [self queueMessage:CreateScrollbarMsgID data:data];
788 - (void)destroyScrollbarWithIdentifier:(long)ident
790 NSMutableData *data = [NSMutableData data];
791 [data appendBytes:&ident length:sizeof(long)];
793 [self queueMessage:DestroyScrollbarMsgID data:data];
796 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
798 NSMutableData *data = [NSMutableData data];
800 [data appendBytes:&ident length:sizeof(long)];
801 [data appendBytes:&visible length:sizeof(int)];
803 [self queueMessage:ShowScrollbarMsgID data:data];
806 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
808 NSMutableData *data = [NSMutableData data];
810 [data appendBytes:&ident length:sizeof(long)];
811 [data appendBytes:&pos length:sizeof(int)];
812 [data appendBytes:&len length:sizeof(int)];
814 [self queueMessage:SetScrollbarPositionMsgID data:data];
817 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
818 identifier:(long)ident
820 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
821 float prop = (float)size/(max+1);
822 if (fval < 0) fval = 0;
823 else if (fval > 1.0f) fval = 1.0f;
824 if (prop < 0) prop = 0;
825 else if (prop > 1.0f) prop = 1.0f;
827 NSMutableData *data = [NSMutableData data];
829 [data appendBytes:&ident length:sizeof(long)];
830 [data appendBytes:&fval length:sizeof(float)];
831 [data appendBytes:&prop length:sizeof(float)];
833 [self queueMessage:SetScrollbarThumbMsgID data:data];
836 - (void)setFont:(NSFont *)font
838 NSString *fontName = [font displayName];
839 float size = [font pointSize];
840 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
842 NSMutableData *data = [NSMutableData data];
844 [data appendBytes:&size length:sizeof(float)];
845 [data appendBytes:&len length:sizeof(int)];
846 [data appendBytes:[fontName UTF8String] length:len];
848 [self queueMessage:SetFontMsgID data:data];
852 - (void)setWideFont:(NSFont *)font
854 NSString *fontName = [font displayName];
855 float size = [font pointSize];
856 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
857 NSMutableData *data = [NSMutableData data];
859 [data appendBytes:&size length:sizeof(float)];
860 [data appendBytes:&len length:sizeof(int)];
862 [data appendBytes:[fontName UTF8String] length:len];
864 [self queueMessage:SetWideFontMsgID data:data];
867 - (void)executeActionWithName:(NSString *)name
869 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
872 NSMutableData *data = [NSMutableData data];
874 [data appendBytes:&len length:sizeof(int)];
875 [data appendBytes:[name UTF8String] length:len];
877 [self queueMessage:ExecuteActionMsgID data:data];
881 - (void)setMouseShape:(int)shape
883 NSMutableData *data = [NSMutableData data];
884 [data appendBytes:&shape length:sizeof(int)];
885 [self queueMessage:SetMouseShapeMsgID data:data];
888 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
890 // Vim specifies times in milliseconds, whereas Cocoa wants them in
892 blinkWaitInterval = .001f*wait;
893 blinkOnInterval = .001f*on;
894 blinkOffInterval = .001f*off;
900 [blinkTimer invalidate];
901 [blinkTimer release];
905 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
907 blinkState = MMBlinkStateOn;
909 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
911 selector:@selector(blinkTimerFired:)
912 userInfo:nil repeats:NO] retain];
913 gui_update_cursor(TRUE, FALSE);
914 [self flushQueue:YES];
920 if (MMBlinkStateOff == blinkState) {
921 gui_update_cursor(TRUE, FALSE);
922 [self flushQueue:YES];
925 blinkState = MMBlinkStateNone;
928 - (void)adjustLinespace:(int)linespace
930 NSMutableData *data = [NSMutableData data];
931 [data appendBytes:&linespace length:sizeof(int)];
932 [self queueMessage:AdjustLinespaceMsgID data:data];
937 [self queueMessage:ActivateMsgID data:nil];
940 - (void)setPreEditRow:(int)row column:(int)col
942 NSMutableData *data = [NSMutableData data];
943 [data appendBytes:&row length:sizeof(int)];
944 [data appendBytes:&col length:sizeof(int)];
945 [self queueMessage:SetPreEditPositionMsgID data:data];
948 - (int)lookupColorWithKey:(NSString *)key
950 if (!(key && [key length] > 0))
953 NSString *stripKey = [[[[key lowercaseString]
954 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
955 componentsSeparatedByString:@" "]
956 componentsJoinedByString:@""];
958 if (stripKey && [stripKey length] > 0) {
959 // First of all try to lookup key in the color dictionary; note that
960 // all keys in this dictionary are lowercase with no whitespace.
961 id obj = [colorDict objectForKey:stripKey];
962 if (obj) return [obj intValue];
964 // The key was not in the dictionary; is it perhaps of the form
966 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
967 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
968 [scanner setScanLocation:1];
970 if ([scanner scanHexInt:&hex]) {
975 // As a last resort, check if it is one of the system defined colors.
976 // The keys in this dictionary are also lowercase with no whitespace.
977 obj = [sysColorDict objectForKey:stripKey];
979 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
982 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
983 [col getRed:&r green:&g blue:&b alpha:&a];
984 return (((int)(r*255+.5f) & 0xff) << 16)
985 + (((int)(g*255+.5f) & 0xff) << 8)
986 + ((int)(b*255+.5f) & 0xff);
991 //NSLog(@"WARNING: No color with key %@ found.", stripKey);
995 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
997 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1000 while ((obj = [e nextObject])) {
1001 if ([value isEqual:obj])
1008 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1010 NSMutableData *data = [NSMutableData data];
1011 [data appendBytes:&fuoptions length:sizeof(int)];
1013 [data appendBytes:&bg length:sizeof(int)];
1014 [self queueMessage:EnterFullscreenMsgID data:data];
1017 - (void)leaveFullscreen
1019 [self queueMessage:LeaveFullscreenMsgID data:nil];
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 // Remove all previous instances of this message from the input queue, else
1042 // the input queue may fill up as a result of Vim not being able to keep up
1043 // with the speed at which new messages are received. This avoids annoying
1044 // situations such as when the keyboard repeat rate is higher than what Vim
1045 // can cope with (which would cause a 'stutter' when scrolling by holding
1046 // down 'j' and then when 'j' was released the screen kept scrolling for a
1049 int i, count = [inputQueue count];
1050 for (i = 1; i < count; i+=2) {
1051 if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1052 [inputQueue removeObjectAtIndex:i];
1053 [inputQueue removeObjectAtIndex:i-1];
1058 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1059 [inputQueue addObject:(data ? (id)data : [NSNull null])];
1062 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1064 // This is just a convenience method that allows the frontend to delay
1065 // sending messages.
1066 int i, count = [messages count];
1067 for (i = 1; i < count; i+=2)
1068 [self processInput:[[messages objectAtIndex:i-1] intValue]
1069 data:[messages objectAtIndex:i]];
1072 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1073 errorString:(out bycopy NSString **)errstr
1075 return evalExprCocoa(expr, errstr);
1079 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1081 NSString *eval = nil;
1082 char_u *s = (char_u*)[expr UTF8String];
1085 s = CONVERT_FROM_UTF8(s);
1088 char_u *res = eval_client_expr_to_string(s);
1091 CONVERT_FROM_UTF8_FREE(s);
1097 s = CONVERT_TO_UTF8(s);
1099 eval = [NSString stringWithUTF8String:(char*)s];
1101 CONVERT_TO_UTF8_FREE(s);
1109 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1111 // TODO: This method should share code with clip_mch_request_selection().
1113 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1114 // If there is no pasteboard, return YES to indicate that there is text
1119 clip_copy_selection();
1121 // Get the text to put on the pasteboard.
1122 long_u llen = 0; char_u *str = 0;
1123 int type = clip_convert_selection(&str, &llen, &clip_star);
1127 // TODO: Avoid overflow.
1128 int len = (int)llen;
1130 if (output_conv.vc_type != CONV_NONE) {
1131 char_u *conv_str = string_convert(&output_conv, str, &len);
1139 NSString *string = [[NSString alloc]
1140 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1142 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1143 [pboard declareTypes:types owner:nil];
1144 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1155 - (oneway void)addReply:(in bycopy NSString *)reply
1156 server:(in byref id <MMVimServerProtocol>)server
1158 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1160 // Replies might come at any time and in any order so we keep them in an
1161 // array inside a dictionary with the send port used as key.
1163 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1164 // HACK! Assume connection uses mach ports.
1165 int port = [(NSMachPort*)[conn sendPort] machPort];
1166 NSNumber *key = [NSNumber numberWithInt:port];
1168 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1170 replies = [NSMutableArray array];
1171 [serverReplyDict setObject:replies forKey:key];
1174 [replies addObject:reply];
1177 - (void)addInput:(in bycopy NSString *)input
1178 client:(in byref id <MMVimClientProtocol>)client
1180 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1182 [self addInput:input];
1183 [self addClient:(id)client];
1186 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1187 client:(in byref id <MMVimClientProtocol>)client
1189 [self addClient:(id)client];
1190 return [self evaluateExpression:expr];
1193 - (void)registerServerWithName:(NSString *)name
1195 NSString *svrName = name;
1196 NSConnection *svrConn = [NSConnection defaultConnection];
1199 for (i = 0; i < MMServerMax; ++i) {
1200 NSString *connName = [self connectionNameFromServerName:svrName];
1202 if ([svrConn registerName:connName]) {
1203 //NSLog(@"Registered server with name: %@", svrName);
1205 // TODO: Set request/reply time-outs to something else?
1207 // Don't wait for requests (time-out means that the message is
1209 [svrConn setRequestTimeout:0];
1210 //[svrConn setReplyTimeout:MMReplyTimeout];
1211 [svrConn setRootObject:self];
1213 char_u *s = (char_u*)[svrName UTF8String];
1215 s = CONVERT_FROM_UTF8(s);
1217 // NOTE: 'serverName' is a global variable
1218 serverName = vim_strsave(s);
1220 CONVERT_FROM_UTF8_FREE(s);
1223 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1226 need_maketitle = TRUE;
1228 [self queueMessage:SetServerNameMsgID data:
1229 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1233 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1237 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1238 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1241 // NOTE: If 'name' equals 'serverName' then the request is local (client
1242 // and server are the same). This case is not handled separately, so a
1243 // connection will be set up anyway (this simplifies the code).
1245 NSConnection *conn = [self connectionForServerName:name];
1248 char_u *s = (char_u*)[name UTF8String];
1250 s = CONVERT_FROM_UTF8(s);
1252 EMSG2(_(e_noserver), s);
1254 CONVERT_FROM_UTF8_FREE(s);
1261 // HACK! Assume connection uses mach ports.
1262 *port = [(NSMachPort*)[conn sendPort] machPort];
1265 id proxy = [conn rootProxy];
1266 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1270 NSString *eval = [proxy evaluateExpression:string client:self];
1273 char_u *r = (char_u*)[eval UTF8String];
1275 r = CONVERT_FROM_UTF8(r);
1277 *reply = vim_strsave(r);
1279 CONVERT_FROM_UTF8_FREE(r);
1282 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1289 [proxy addInput:string client:self];
1292 @catch (NSException *e) {
1293 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1300 - (NSArray *)serverList
1302 NSArray *list = nil;
1304 if ([self connection]) {
1305 id proxy = [connection rootProxy];
1306 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1309 list = [proxy serverList];
1311 @catch (NSException *e) {
1312 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1315 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1321 - (NSString *)peekForReplyOnPort:(int)port
1323 //NSLog(@"%s%d", _cmd, port);
1325 NSNumber *key = [NSNumber numberWithInt:port];
1326 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1327 if (replies && [replies count]) {
1328 //NSLog(@" %d replies, topmost is: %@", [replies count],
1329 // [replies objectAtIndex:0]);
1330 return [replies objectAtIndex:0];
1333 //NSLog(@" No replies");
1337 - (NSString *)waitForReplyOnPort:(int)port
1339 //NSLog(@"%s%d", _cmd, port);
1341 NSConnection *conn = [self connectionForServerPort:port];
1345 NSNumber *key = [NSNumber numberWithInt:port];
1346 NSMutableArray *replies = nil;
1347 NSString *reply = nil;
1349 // Wait for reply as long as the connection to the server is valid (unless
1350 // user interrupts wait with Ctrl-C).
1351 while (!got_int && [conn isValid] &&
1352 !(replies = [serverReplyDict objectForKey:key])) {
1353 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1354 beforeDate:[NSDate distantFuture]];
1358 if ([replies count] > 0) {
1359 reply = [[replies objectAtIndex:0] retain];
1360 //NSLog(@" Got reply: %@", reply);
1361 [replies removeObjectAtIndex:0];
1362 [reply autorelease];
1365 if ([replies count] == 0)
1366 [serverReplyDict removeObjectForKey:key];
1372 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1374 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1377 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1378 [client addReply:reply server:self];
1381 @catch (NSException *e) {
1382 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1385 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1396 - (void)setWaitForAck:(BOOL)yn
1401 - (void)waitForConnectionAcknowledgement
1403 if (!waitForAck) return;
1405 while (waitForAck && !got_int && [connection isValid] && !isTerminating) {
1406 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1407 beforeDate:[NSDate distantFuture]];
1408 //NSLog(@" waitForAck=%d got_int=%d isTerminating=%d isValid=%d",
1409 // waitForAck, got_int, isTerminating, [connection isValid]);
1413 // Never received a connection acknowledgement, so die.
1414 [[NSNotificationCenter defaultCenter] removeObserver:self];
1415 [frontendProxy release]; frontendProxy = nil;
1417 // NOTE: We intentionally do not call mch_exit() since this in turn
1418 // will lead to -[MMBackend exit] getting called which we want to
1420 usleep(MMExitProcessDelay);
1424 [self processInputQueue];
1427 - (oneway void)acknowledgeConnection
1429 //NSLog(@"%s", _cmd);
1437 @implementation MMBackend (Private)
1439 - (void)waitForDialogReturn
1441 // Keep processing the run loop until a dialog returns. To avoid getting
1442 // stuck in an endless loop (could happen if the setDialogReturn: message
1443 // was lost) we also do some paranoia checks.
1445 // Note that in Cocoa the user can still resize windows and select menu
1446 // items while a sheet is being displayed, so we can't just wait for the
1447 // first message to arrive and assume that is the setDialogReturn: call.
1449 while (nil == dialogReturn && !got_int && [connection isValid]
1451 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1452 beforeDate:[NSDate distantFuture]];
1454 // Search for any resize messages on the input queue. All other messages
1455 // on the input queue are dropped. The reason why we single out resize
1456 // messages is because the user may have resized the window while a sheet
1458 int i, count = [inputQueue count];
1460 id textDimData = nil;
1462 for (i = count-2; i >= 0; i -= 2) {
1463 int msgid = [[inputQueue objectAtIndex:i] intValue];
1464 if (SetTextDimensionsMsgID == msgid) {
1465 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1471 [inputQueue removeAllObjects];
1474 [inputQueue addObject:
1475 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1476 [inputQueue addObject:textDimData];
1477 [textDimData release];
1482 - (void)insertVimStateMessage
1484 // NOTE: This is the place to add Vim state that needs to be accessed from
1485 // MacVim. Do not add state that could potentially require lots of memory
1486 // since this message gets sent each time the output queue is forcibly
1487 // flushed (e.g. storing the currently selected text would be a bad idea).
1488 // We take this approach of "pushing" the state to MacVim to avoid having
1489 // to make synchronous calls from MacVim to Vim in order to get state.
1491 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1492 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1493 [NSNumber numberWithInt:p_mh], @"p_mh",
1494 [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1497 // Put the state before all other messages.
1498 int msgid = SetVimStateMsgID;
1499 [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1500 [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1504 - (void)processInputQueue
1506 if ([inputQueue count] == 0) return;
1508 // NOTE: One of the input events may cause this method to be called
1509 // recursively, so copy the input queue to a local variable and clear the
1510 // queue before starting to process input events (otherwise we could get
1511 // stuck in an endless loop).
1512 NSArray *q = [inputQueue copy];
1513 unsigned i, count = [q count];
1515 [inputQueue removeAllObjects];
1517 for (i = 1; i < count; i+=2) {
1518 int msgid = [[q objectAtIndex:i-1] intValue];
1519 id data = [q objectAtIndex:i];
1520 if ([data isEqual:[NSNull null]])
1523 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1524 [self handleInputEvent:msgid data:data];
1528 //NSLog(@"Clear input event queue");
1531 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1533 if (InsertTextMsgID == msgid) {
1534 [self handleInsertText:data];
1535 } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1537 const void *bytes = [data bytes];
1538 int mods = *((int*)bytes); bytes += sizeof(int);
1539 int len = *((int*)bytes); bytes += sizeof(int);
1540 NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1541 encoding:NSUTF8StringEncoding];
1542 mods = eventModifierFlagsToVimModMask(mods);
1544 [self handleKeyDown:key modifiers:mods];
1547 } else if (ScrollWheelMsgID == msgid) {
1549 const void *bytes = [data bytes];
1551 int row = *((int*)bytes); bytes += sizeof(int);
1552 int col = *((int*)bytes); bytes += sizeof(int);
1553 int flags = *((int*)bytes); bytes += sizeof(int);
1554 float dy = *((float*)bytes); bytes += sizeof(float);
1556 int button = MOUSE_5;
1557 if (dy > 0) button = MOUSE_4;
1559 flags = eventModifierFlagsToVimMouseModMask(flags);
1561 int numLines = (int)round(dy);
1562 if (numLines < 0) numLines = -numLines;
1563 if (numLines == 0) numLines = 1;
1565 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1566 gui.scroll_wheel_force = numLines;
1569 gui_send_mouse_event(button, col, row, NO, flags);
1570 } else if (MouseDownMsgID == msgid) {
1572 const void *bytes = [data bytes];
1574 int row = *((int*)bytes); bytes += sizeof(int);
1575 int col = *((int*)bytes); bytes += sizeof(int);
1576 int button = *((int*)bytes); bytes += sizeof(int);
1577 int flags = *((int*)bytes); bytes += sizeof(int);
1578 int count = *((int*)bytes); bytes += sizeof(int);
1580 button = eventButtonNumberToVimMouseButton(button);
1582 flags = eventModifierFlagsToVimMouseModMask(flags);
1583 gui_send_mouse_event(button, col, row, count>1, flags);
1585 } else if (MouseUpMsgID == msgid) {
1587 const void *bytes = [data bytes];
1589 int row = *((int*)bytes); bytes += sizeof(int);
1590 int col = *((int*)bytes); bytes += sizeof(int);
1591 int flags = *((int*)bytes); bytes += sizeof(int);
1593 flags = eventModifierFlagsToVimMouseModMask(flags);
1595 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1596 } else if (MouseDraggedMsgID == msgid) {
1598 const void *bytes = [data bytes];
1600 int row = *((int*)bytes); bytes += sizeof(int);
1601 int col = *((int*)bytes); bytes += sizeof(int);
1602 int flags = *((int*)bytes); bytes += sizeof(int);
1604 flags = eventModifierFlagsToVimMouseModMask(flags);
1606 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1607 } else if (MouseMovedMsgID == msgid) {
1608 const void *bytes = [data bytes];
1609 int row = *((int*)bytes); bytes += sizeof(int);
1610 int col = *((int*)bytes); bytes += sizeof(int);
1612 gui_mouse_moved(col, row);
1613 } else if (AddInputMsgID == msgid) {
1614 NSString *string = [[NSString alloc] initWithData:data
1615 encoding:NSUTF8StringEncoding];
1617 [self addInput:string];
1620 } else if (TerminateNowMsgID == msgid) {
1621 isTerminating = YES;
1622 } else if (SelectTabMsgID == msgid) {
1624 const void *bytes = [data bytes];
1625 int idx = *((int*)bytes) + 1;
1626 //NSLog(@"Selecting tab %d", idx);
1627 send_tabline_event(idx);
1628 } else if (CloseTabMsgID == msgid) {
1630 const void *bytes = [data bytes];
1631 int idx = *((int*)bytes) + 1;
1632 //NSLog(@"Closing tab %d", idx);
1633 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1634 } else if (AddNewTabMsgID == msgid) {
1635 //NSLog(@"Adding new tab");
1636 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1637 } else if (DraggedTabMsgID == msgid) {
1639 const void *bytes = [data bytes];
1640 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1642 int idx = *((int*)bytes);
1645 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1646 || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1648 const void *bytes = [data bytes];
1650 if (SetTextColumnsMsgID != msgid) {
1651 rows = *((int*)bytes); bytes += sizeof(int);
1654 if (SetTextRowsMsgID != msgid) {
1655 cols = *((int*)bytes); bytes += sizeof(int);
1659 if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1660 int dim[2] = { rows, cols };
1661 d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1662 msgid = SetTextDimensionsMsgID;
1665 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1666 // gui_resize_shell(), so we have to manually set the rows and columns
1667 // here. (MacVim doesn't change the rows and columns to avoid
1668 // inconsistent states between Vim and MacVim.)
1669 [self queueMessage:msgid data:d];
1671 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1672 gui_resize_shell(cols, rows);
1673 } else if (ExecuteMenuMsgID == msgid) {
1674 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1676 NSArray *desc = [attrs objectForKey:@"descriptor"];
1677 vimmenu_T *menu = menu_for_descriptor(desc);
1681 } else if (ToggleToolbarMsgID == msgid) {
1682 [self handleToggleToolbar];
1683 } else if (ScrollbarEventMsgID == msgid) {
1684 [self handleScrollbarEvent:data];
1685 } else if (SetFontMsgID == msgid) {
1686 [self handleSetFont:data];
1687 } else if (VimShouldCloseMsgID == msgid) {
1689 } else if (DropFilesMsgID == msgid) {
1690 [self handleDropFiles:data];
1691 } else if (DropStringMsgID == msgid) {
1692 [self handleDropString:data];
1693 } else if (GotFocusMsgID == msgid) {
1695 [self focusChange:YES];
1696 } else if (LostFocusMsgID == msgid) {
1698 [self focusChange:NO];
1699 } else if (SetMouseShapeMsgID == msgid) {
1700 const void *bytes = [data bytes];
1701 int shape = *((int*)bytes); bytes += sizeof(int);
1702 update_mouseshape(shape);
1703 } else if (XcodeModMsgID == msgid) {
1704 [self handleXcodeMod:data];
1705 } else if (OpenWithArgumentsMsgID == msgid) {
1706 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1708 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1712 + (NSDictionary *)specialKeys
1714 static NSDictionary *specialKeys = nil;
1717 NSBundle *mainBundle = [NSBundle mainBundle];
1718 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1720 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1726 - (void)handleInsertText:(NSData *)data
1730 NSString *key = [[NSString alloc] initWithData:data
1731 encoding:NSUTF8StringEncoding];
1732 char_u *str = (char_u*)[key UTF8String];
1733 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1736 char_u *conv_str = NULL;
1737 if (input_conv.vc_type != CONV_NONE) {
1738 conv_str = string_convert(&input_conv, str, &len);
1744 if (len == 1 && ((str[0] == Ctrl_C && ctrl_c_interrupts)
1745 || (str[0] == intr_char && intr_char != Ctrl_C))) {
1750 for (i = 0; i < len; ++i) {
1751 add_to_input_buf(str+i, 1);
1752 if (CSI == str[i]) {
1753 // NOTE: If the converted string contains the byte CSI, then it
1754 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1756 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1757 add_to_input_buf(extra, 2);
1768 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1772 char_u *chars = (char_u*)[key UTF8String];
1774 char_u *conv_str = NULL;
1776 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1778 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1779 // that new keys can easily be added.
1780 NSString *specialString = [[MMBackend specialKeys]
1782 if (specialString && [specialString length] > 1) {
1783 //NSLog(@"special key: %@", specialString);
1784 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1785 [specialString characterAtIndex:1]);
1787 ikey = simplify_key(ikey, &mods);
1792 special[1] = K_SECOND(ikey);
1793 special[2] = K_THIRD(ikey);
1797 } else if (1 == length && TAB == chars[0]) {
1798 // Tab is a trouble child:
1799 // - <Tab> is added to the input buffer as is
1800 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1801 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1802 // to be converted to utf-8
1803 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1804 // - <C-Tab> is reserved by Mac OS X
1805 // - <D-Tab> is reserved by Mac OS X
1810 if (mods & MOD_MASK_SHIFT) {
1811 mods &= ~MOD_MASK_SHIFT;
1813 special[1] = K_SECOND(K_S_TAB);
1814 special[2] = K_THIRD(K_S_TAB);
1816 } else if (mods & MOD_MASK_ALT) {
1817 int mtab = 0x80 | TAB;
1821 special[0] = (mtab >> 6) + 0xc0;
1822 special[1] = mtab & 0xbf;
1830 mods &= ~MOD_MASK_ALT;
1832 } else if (length > 0) {
1833 unichar c = [key characterAtIndex:0];
1835 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1836 // [key characterAtIndex:0], mods);
1838 if (length == 1 && ((c == Ctrl_C && ctrl_c_interrupts)
1839 || (c == intr_char && intr_char != Ctrl_C))) {
1844 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1845 // cleared since they are already added to the key by the AppKit.
1846 // Unfortunately, the only way to deal with when to clear the modifiers
1847 // or not seems to be to have hard-wired rules like this.
1848 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1849 || 0x9 == c || 0xd == c || ESC == c) ) {
1850 mods &= ~MOD_MASK_SHIFT;
1851 mods &= ~MOD_MASK_CTRL;
1852 //NSLog(@"clear shift ctrl");
1855 // HACK! All Option+key presses go via 'insert text' messages, except
1856 // for <M-Space>. If the Alt flag is not cleared for <M-Space> it does
1857 // not work to map to it.
1858 if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1859 //NSLog(@"clear alt");
1860 mods &= ~MOD_MASK_ALT;
1864 if (input_conv.vc_type != CONV_NONE) {
1865 conv_str = string_convert(&input_conv, chars, &length);
1872 if (chars && length > 0) {
1874 //NSLog(@"adding mods: %d", mods);
1876 modChars[1] = KS_MODIFIER;
1878 add_to_input_buf(modChars, 3);
1881 //NSLog(@"add to input buf: 0x%x", chars[0]);
1882 // TODO: Check for CSI bytes?
1883 add_to_input_buf(chars, length);
1892 - (void)queueMessage:(int)msgid data:(NSData *)data
1894 //if (msgid != EnableMenuItemMsgID)
1895 // NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1897 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1899 [outputQueue addObject:data];
1901 [outputQueue addObject:[NSData data]];
1904 - (void)connectionDidDie:(NSNotification *)notification
1906 // If the main connection to MacVim is lost this means that MacVim was
1907 // either quit (by the user chosing Quit on the MacVim menu), or it has
1908 // crashed. In the former case the flag 'isTerminating' is set and we then
1909 // quit cleanly; in the latter case we make sure the swap files are left
1912 // NOTE: This is not called if a Vim controller invalidates its connection.
1914 //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1918 getout_preserve_modified(1);
1921 - (void)blinkTimerFired:(NSTimer *)timer
1923 NSTimeInterval timeInterval = 0;
1925 [blinkTimer release];
1928 if (MMBlinkStateOn == blinkState) {
1929 gui_undraw_cursor();
1930 blinkState = MMBlinkStateOff;
1931 timeInterval = blinkOffInterval;
1932 } else if (MMBlinkStateOff == blinkState) {
1933 gui_update_cursor(TRUE, FALSE);
1934 blinkState = MMBlinkStateOn;
1935 timeInterval = blinkOnInterval;
1938 if (timeInterval > 0) {
1940 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1941 selector:@selector(blinkTimerFired:)
1942 userInfo:nil repeats:NO] retain];
1943 [self flushQueue:YES];
1947 - (void)focusChange:(BOOL)on
1949 gui_focus_change(on);
1952 - (void)handleToggleToolbar
1954 // If 'go' contains 'T', then remove it, else add it.
1956 char_u go[sizeof(GO_ALL)+2];
1961 p = vim_strchr(go, GO_TOOLBAR);
1965 char_u *end = go + len;
1971 go[len] = GO_TOOLBAR;
1975 set_option_value((char_u*)"guioptions", 0, go, 0);
1977 // Force screen redraw (does it have to be this complicated?).
1978 redraw_all_later(CLEAR);
1979 update_screen(NOT_VALID);
1982 gui_update_cursor(FALSE, FALSE);
1986 - (void)handleScrollbarEvent:(NSData *)data
1990 const void *bytes = [data bytes];
1991 long ident = *((long*)bytes); bytes += sizeof(long);
1992 int hitPart = *((int*)bytes); bytes += sizeof(int);
1993 float fval = *((float*)bytes); bytes += sizeof(float);
1994 scrollbar_T *sb = gui_find_scrollbar(ident);
1997 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1998 long value = sb_info->value;
1999 long size = sb_info->size;
2000 long max = sb_info->max;
2001 BOOL isStillDragging = NO;
2002 BOOL updateKnob = YES;
2005 case NSScrollerDecrementPage:
2006 value -= (size > 2 ? size - 2 : 1);
2008 case NSScrollerIncrementPage:
2009 value += (size > 2 ? size - 2 : 1);
2011 case NSScrollerDecrementLine:
2014 case NSScrollerIncrementLine:
2017 case NSScrollerKnob:
2018 isStillDragging = YES;
2020 case NSScrollerKnobSlot:
2021 value = (long)(fval * (max - size + 1));
2028 //NSLog(@"value %d -> %d", sb_info->value, value);
2029 gui_drag_scrollbar(sb, value, isStillDragging);
2032 // Dragging the knob or option+clicking automatically updates
2033 // the knob position (on the actual NSScroller), so we only
2034 // need to set the knob position in the other cases.
2036 // Update both the left&right vertical scrollbars.
2037 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2038 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2039 [self setScrollbarThumbValue:value size:size max:max
2040 identifier:identLeft];
2041 [self setScrollbarThumbValue:value size:size max:max
2042 identifier:identRight];
2044 // Update the horizontal scrollbar.
2045 [self setScrollbarThumbValue:value size:size max:max
2052 - (void)handleSetFont:(NSData *)data
2056 const void *bytes = [data bytes];
2057 float pointSize = *((float*)bytes); bytes += sizeof(float);
2058 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2059 bytes += sizeof(unsigned); // len not used
2061 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2062 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2063 char_u *s = (char_u*)[name UTF8String];
2066 s = CONVERT_FROM_UTF8(s);
2069 set_option_value((char_u*)"guifont", 0, s, 0);
2072 CONVERT_FROM_UTF8_FREE(s);
2075 // Force screen redraw (does it have to be this complicated?).
2076 redraw_all_later(CLEAR);
2077 update_screen(NOT_VALID);
2080 gui_update_cursor(FALSE, FALSE);
2084 - (void)handleDropFiles:(NSData *)data
2086 // TODO: Get rid of this method; instead use Vim script directly. At the
2087 // moment I know how to do this to open files in tabs, but I'm not sure how
2088 // to add the filenames to the command line when in command line mode.
2092 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2095 id obj = [args objectForKey:@"forceOpen"];
2096 BOOL forceOpen = YES;
2098 forceOpen = [obj boolValue];
2100 NSArray *filenames = [args objectForKey:@"filenames"];
2101 if (!(filenames && [filenames count] > 0)) return;
2104 if (!forceOpen && (State & CMDLINE)) {
2105 // HACK! If Vim is in command line mode then the files names
2106 // should be added to the command line, instead of opening the
2107 // files in tabs (unless forceOpen is set). This is taken care of by
2108 // gui_handle_drop().
2109 int n = [filenames count];
2110 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2113 for (i = 0; i < n; ++i)
2114 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2116 // NOTE! This function will free 'fnames'.
2117 // HACK! It is assumed that the 'x' and 'y' arguments are
2118 // unused when in command line mode.
2119 gui_handle_drop(0, 0, 0, fnames, n);
2124 [self handleOpenWithArguments:args];
2128 - (void)handleDropString:(NSData *)data
2133 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2134 const void *bytes = [data bytes];
2135 int len = *((int*)bytes); bytes += sizeof(int);
2136 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2138 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2139 NSRange range = { 0, [string length] };
2140 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2141 withString:@"\x0a" options:0
2144 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2145 options:0 range:range];
2148 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2149 char_u *s = (char_u*)[string UTF8String];
2151 if (input_conv.vc_type != CONV_NONE)
2152 s = string_convert(&input_conv, s, &len);
2154 dnd_yank_drag_data(s, len);
2156 if (input_conv.vc_type != CONV_NONE)
2159 add_to_input_buf(dropkey, sizeof(dropkey));
2163 - (void)startOdbEditWithArguments:(NSDictionary *)args
2165 #ifdef FEAT_ODB_EDITOR
2166 id obj = [args objectForKey:@"remoteID"];
2169 OSType serverID = [obj unsignedIntValue];
2170 NSString *remotePath = [args objectForKey:@"remotePath"];
2172 NSAppleEventDescriptor *token = nil;
2173 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2174 obj = [args objectForKey:@"remoteTokenDescType"];
2175 if (tokenData && obj) {
2176 DescType tokenType = [obj unsignedLongValue];
2177 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2181 NSArray *filenames = [args objectForKey:@"filenames"];
2182 unsigned i, numFiles = [filenames count];
2183 for (i = 0; i < numFiles; ++i) {
2184 NSString *filename = [filenames objectAtIndex:i];
2185 char_u *s = [filename vimStringSave];
2186 buf_T *buf = buflist_findname(s);
2190 if (buf->b_odb_token) {
2191 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2192 buf->b_odb_token = NULL;
2195 if (buf->b_odb_fname) {
2196 vim_free(buf->b_odb_fname);
2197 buf->b_odb_fname = NULL;
2200 buf->b_odb_server_id = serverID;
2203 buf->b_odb_token = [token retain];
2205 buf->b_odb_fname = [remotePath vimStringSave];
2207 NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2211 #endif // FEAT_ODB_EDITOR
2214 - (void)handleXcodeMod:(NSData *)data
2217 const void *bytes = [data bytes];
2218 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2219 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2223 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2224 descriptorWithDescriptorType:type
2230 - (void)handleOpenWithArguments:(NSDictionary *)args
2232 // ARGUMENT: DESCRIPTION:
2233 // -------------------------------------------------------------
2234 // filenames list of filenames
2235 // dontOpen don't open files specified in above argument
2236 // layout which layout to use to open files
2237 // selectionRange range of lines to select
2238 // searchText string to search for
2239 // cursorLine line to position the cursor on
2240 // cursorColumn column to position the cursor on
2241 // (only valid when "cursorLine" is set)
2242 // remoteID ODB parameter
2243 // remotePath ODB parameter
2244 // remoteTokenDescType ODB parameter
2245 // remoteTokenData ODB parameter
2247 //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2249 NSArray *filenames = [args objectForKey:@"filenames"];
2250 int i, numFiles = filenames ? [filenames count] : 0;
2251 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2252 int layout = [[args objectForKey:@"layout"] intValue];
2254 // Change to directory of first file to open if this is an "unused" editor
2255 // (but do not do this if editing remotely).
2256 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2257 && (starting || [self unusedEditor]) ) {
2258 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2264 // When Vim is starting we simply add the files to be opened to the
2265 // global arglist and Vim will take care of opening them for us.
2266 if (openFiles && numFiles > 0) {
2267 for (i = 0; i < numFiles; i++) {
2268 NSString *fname = [filenames objectAtIndex:i];
2271 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2272 || (p = [fname vimStringSave]) == NULL)
2273 exit(2); // See comment in -[MMBackend exit]
2275 alist_add(&global_alist, p, 2);
2278 // Vim will take care of arranging the files added to the arglist
2279 // in windows or tabs; all we must do is to specify which layout to
2281 initialWindowLayout = layout;
2284 // When Vim is already open we resort to some trickery to open the
2285 // files with the specified layout.
2287 // TODO: Figure out a better way to handle this?
2288 if (openFiles && numFiles > 0) {
2289 BOOL oneWindowInTab = topframe ? YES
2290 : (topframe->fr_layout == FR_LEAF);
2291 BOOL bufChanged = NO;
2292 BOOL bufHasFilename = NO;
2294 bufChanged = curbufIsChanged();
2295 bufHasFilename = curbuf->b_ffname != NULL;
2298 // Temporarily disable flushing since the following code may
2299 // potentially cause multiple redraws.
2300 flushDisabled = YES;
2302 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2303 if (WIN_TABS == layout && !onlyOneTab) {
2304 // By going to the last tabpage we ensure that the new tabs
2305 // will appear last (if this call is left out, the taborder
2310 // Make sure we're in normal mode first.
2311 [self addInput:@"<C-\\><C-N>"];
2314 // With "split layout" we open a new tab before opening
2315 // multiple files if the current tab has more than one window
2316 // or if there is exactly one window but whose buffer has a
2317 // filename. (The :drop command ensures modified buffers get
2318 // their own window.)
2319 if ((WIN_HOR == layout || WIN_VER == layout) &&
2320 (!oneWindowInTab || bufHasFilename))
2321 [self addInput:@":tabnew<CR>"];
2323 // The files are opened by constructing a ":drop ..." command
2324 // and executing it.
2325 NSMutableString *cmd = (WIN_TABS == layout)
2326 ? [NSMutableString stringWithString:@":tab drop"]
2327 : [NSMutableString stringWithString:@":drop"];
2329 for (i = 0; i < numFiles; ++i) {
2330 NSString *file = [filenames objectAtIndex:i];
2331 file = [file stringByEscapingSpecialFilenameCharacters];
2332 [cmd appendString:@" "];
2333 [cmd appendString:file];
2336 // Temporarily clear 'suffixes' so that the files are opened in
2337 // the same order as they appear in the "filenames" array.
2338 [self addInput:@":let t:mvim_oldsu=&su|set su=<CR>"];
2340 [self addInput:cmd];
2342 // Split the view into multiple windows if requested.
2343 if (WIN_HOR == layout)
2344 [self addInput:@"|sall"];
2345 else if (WIN_VER == layout)
2346 [self addInput:@"|vert sall"];
2348 // Restore the old value of 'suffixes'.
2349 [self addInput:@"|let &su=t:mvim_oldsu|unlet t:mvim_oldsu"];
2351 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2352 [self addInput:@"|redr|f<CR>"];
2354 // When opening one file we try to reuse the current window,
2355 // but not if its buffer is modified or has a filename.
2356 // However, the 'arglist' layout always opens the file in the
2358 NSString *file = [[filenames lastObject]
2359 stringByEscapingSpecialFilenameCharacters];
2361 if (WIN_HOR == layout) {
2362 if (!(bufHasFilename || bufChanged))
2363 cmd = [NSString stringWithFormat:@":e %@", file];
2365 cmd = [NSString stringWithFormat:@":sp %@", file];
2366 } else if (WIN_VER == layout) {
2367 if (!(bufHasFilename || bufChanged))
2368 cmd = [NSString stringWithFormat:@":e %@", file];
2370 cmd = [NSString stringWithFormat:@":vsp %@", file];
2371 } else if (WIN_TABS == layout) {
2372 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2373 cmd = [NSString stringWithFormat:@":e %@", file];
2375 cmd = [NSString stringWithFormat:@":tabe %@", file];
2377 // (The :drop command will split if there is a modified
2379 cmd = [NSString stringWithFormat:@":drop %@", file];
2382 [self addInput:cmd];
2384 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2385 [self addInput:@"|redr|f<CR>"];
2388 // Force screen redraw (does it have to be this complicated?).
2389 // (This code was taken from the end of gui_handle_drop().)
2390 update_screen(NOT_VALID);
2393 gui_update_cursor(FALSE, FALSE);
2401 if ([args objectForKey:@"remoteID"]) {
2402 // NOTE: We have to delay processing any ODB related arguments since
2403 // the file(s) may not be opened until the input buffer is processed.
2404 [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2409 NSString *lineString = [args objectForKey:@"cursorLine"];
2410 if (lineString && [lineString intValue] > 0) {
2411 NSString *columnString = [args objectForKey:@"cursorColumn"];
2412 if (!(columnString && [columnString intValue] > 0))
2413 columnString = @"1";
2415 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2416 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2417 [self addInput:cmd];
2420 NSString *rangeString = [args objectForKey:@"selectionRange"];
2422 // Build a command line string that will select the given range of
2423 // lines. If range.length == 0, then position the cursor on the given
2424 // line but do not select.
2425 NSRange range = NSRangeFromString(rangeString);
2427 if (range.length > 0) {
2428 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2429 NSMaxRange(range), range.location];
2431 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2435 [self addInput:cmd];
2438 NSString *searchText = [args objectForKey:@"searchText"];
2440 // TODO: Searching is an exclusive motion, so if the pattern would
2441 // match on row 0 column 0 then this pattern will miss that match.
2442 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@<CR>",
2447 - (BOOL)checkForModifiedBuffers
2450 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2451 if (bufIsChanged(buf)) {
2459 - (void)addInput:(NSString *)input
2461 char_u *s = (char_u*)[input UTF8String];
2464 s = CONVERT_FROM_UTF8(s);
2467 server_to_input_buf(s);
2470 CONVERT_FROM_UTF8_FREE(s);
2474 - (BOOL)unusedEditor
2476 BOOL oneWindowInTab = topframe ? YES
2477 : (topframe->fr_layout == FR_LEAF);
2478 BOOL bufChanged = NO;
2479 BOOL bufHasFilename = NO;
2481 bufChanged = curbufIsChanged();
2482 bufHasFilename = curbuf->b_ffname != NULL;
2485 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2487 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2490 @end // MMBackend (Private)
2495 @implementation MMBackend (ClientServer)
2497 - (NSString *)connectionNameFromServerName:(NSString *)name
2499 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2501 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2505 - (NSConnection *)connectionForServerName:(NSString *)name
2507 // TODO: Try 'name%d' if 'name' fails.
2508 NSString *connName = [self connectionNameFromServerName:name];
2509 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2512 svrConn = [NSConnection connectionWithRegisteredName:connName
2514 // Try alternate server...
2515 if (!svrConn && alternateServerName) {
2516 //NSLog(@" trying to connect to alternate server: %@",
2517 // alternateServerName);
2518 connName = [self connectionNameFromServerName:alternateServerName];
2519 svrConn = [NSConnection connectionWithRegisteredName:connName
2523 // Try looking for alternate servers...
2525 //NSLog(@" looking for alternate servers...");
2526 NSString *alt = [self alternateServerNameForName:name];
2527 if (alt != alternateServerName) {
2528 //NSLog(@" found alternate server: %@", string);
2529 [alternateServerName release];
2530 alternateServerName = [alt copy];
2534 // Try alternate server again...
2535 if (!svrConn && alternateServerName) {
2536 //NSLog(@" trying to connect to alternate server: %@",
2537 // alternateServerName);
2538 connName = [self connectionNameFromServerName:alternateServerName];
2539 svrConn = [NSConnection connectionWithRegisteredName:connName
2544 [connectionNameDict setObject:svrConn forKey:connName];
2546 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2547 [[NSNotificationCenter defaultCenter] addObserver:self
2548 selector:@selector(serverConnectionDidDie:)
2549 name:NSConnectionDidDieNotification object:svrConn];
2556 - (NSConnection *)connectionForServerPort:(int)port
2559 NSEnumerator *e = [connectionNameDict objectEnumerator];
2561 while ((conn = [e nextObject])) {
2562 // HACK! Assume connection uses mach ports.
2563 if (port == [(NSMachPort*)[conn sendPort] machPort])
2570 - (void)serverConnectionDidDie:(NSNotification *)notification
2572 //NSLog(@"%s%@", _cmd, notification);
2574 NSConnection *svrConn = [notification object];
2576 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2577 [[NSNotificationCenter defaultCenter]
2579 name:NSConnectionDidDieNotification
2582 [connectionNameDict removeObjectsForKeys:
2583 [connectionNameDict allKeysForObject:svrConn]];
2585 // HACK! Assume connection uses mach ports.
2586 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2587 NSNumber *key = [NSNumber numberWithInt:port];
2589 [clientProxyDict removeObjectForKey:key];
2590 [serverReplyDict removeObjectForKey:key];
2593 - (void)addClient:(NSDistantObject *)client
2595 NSConnection *conn = [client connectionForProxy];
2596 // HACK! Assume connection uses mach ports.
2597 int port = [(NSMachPort*)[conn sendPort] machPort];
2598 NSNumber *key = [NSNumber numberWithInt:port];
2600 if (![clientProxyDict objectForKey:key]) {
2601 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2602 [clientProxyDict setObject:client forKey:key];
2605 // NOTE: 'clientWindow' is a global variable which is used by <client>
2606 clientWindow = port;
2609 - (NSString *)alternateServerNameForName:(NSString *)name
2611 if (!(name && [name length] > 0))
2614 // Only look for alternates if 'name' doesn't end in a digit.
2615 unichar lastChar = [name characterAtIndex:[name length]-1];
2616 if (lastChar >= '0' && lastChar <= '9')
2619 // Look for alternates among all current servers.
2620 NSArray *list = [self serverList];
2621 if (!(list && [list count] > 0))
2624 // Filter out servers starting with 'name' and ending with a number. The
2625 // (?i) pattern ensures that the match is case insensitive.
2626 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2627 NSPredicate *pred = [NSPredicate predicateWithFormat:
2628 @"SELF MATCHES %@", pat];
2629 list = [list filteredArrayUsingPredicate:pred];
2630 if ([list count] > 0) {
2631 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2632 return [list objectAtIndex:0];
2638 @end // MMBackend (ClientServer)
2643 @implementation NSString (MMServerNameCompare)
2644 - (NSComparisonResult)serverNameCompare:(NSString *)string
2646 return [self compare:string
2647 options:NSCaseInsensitiveSearch|NSNumericSearch];
2654 static int eventModifierFlagsToVimModMask(int modifierFlags)
2658 if (modifierFlags & NSShiftKeyMask)
2659 modMask |= MOD_MASK_SHIFT;
2660 if (modifierFlags & NSControlKeyMask)
2661 modMask |= MOD_MASK_CTRL;
2662 if (modifierFlags & NSAlternateKeyMask)
2663 modMask |= MOD_MASK_ALT;
2664 if (modifierFlags & NSCommandKeyMask)
2665 modMask |= MOD_MASK_CMD;
2670 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2674 if (modifierFlags & NSShiftKeyMask)
2675 modMask |= MOUSE_SHIFT;
2676 if (modifierFlags & NSControlKeyMask)
2677 modMask |= MOUSE_CTRL;
2678 if (modifierFlags & NSAlternateKeyMask)
2679 modMask |= MOUSE_ALT;
2684 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2686 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2688 return (buttonNumber >= 0 && buttonNumber < 3)
2689 ? mouseButton[buttonNumber] : -1;
2694 // This function is modeled after the VimToPython function found in if_python.c
2695 // NB This does a deep copy by value, it does not lookup references like the
2696 // VimToPython function does. This is because I didn't want to deal with the
2697 // retain cycles that this would create, and we can cover 99% of the use cases
2698 // by ignoring it. If we ever switch to using GC in MacVim then this
2699 // functionality can be implemented easily.
2700 static id vimToCocoa(typval_T * tv, int depth)
2706 // Avoid infinite recursion
2711 if (tv->v_type == VAR_STRING) {
2712 char_u * val = tv->vval.v_string;
2713 // val can be NULL if the string is empty
2715 result = [NSString string];
2718 val = CONVERT_TO_UTF8(val);
2720 result = [NSString stringWithUTF8String:(char*)val];
2722 CONVERT_TO_UTF8_FREE(val);
2725 } else if (tv->v_type == VAR_NUMBER) {
2726 // looks like sizeof(varnumber_T) is always <= sizeof(long)
2727 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2728 } else if (tv->v_type == VAR_LIST) {
2729 list_T * list = tv->vval.v_list;
2732 NSMutableArray * arr = result = [NSMutableArray array];
2735 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2736 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2737 [arr addObject:newObj];
2740 } else if (tv->v_type == VAR_DICT) {
2741 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2743 if (tv->vval.v_dict != NULL) {
2744 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2745 int todo = ht->ht_used;
2749 for (hi = ht->ht_array; todo > 0; ++hi) {
2750 if (!HASHITEM_EMPTY(hi)) {
2753 di = dict_lookup(hi);
2754 newObj = vimToCocoa(&di->di_tv, depth + 1);
2756 char_u * keyval = hi->hi_key;
2758 keyval = CONVERT_TO_UTF8(keyval);
2760 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2762 CONVERT_TO_UTF8_FREE(keyval);
2764 [dict setObject:newObj forKey:key];
2768 } else { // only func refs should fall into this category?
2776 // This function is modeled after eval_client_expr_to_string found in main.c
2777 // Returns nil if there was an error evaluating the expression, and writes a
2778 // message to errorStr.
2779 // TODO Get the error that occurred while evaluating the expression in vim
2781 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2784 char_u *s = (char_u*)[expr UTF8String];
2787 s = CONVERT_FROM_UTF8(s);
2790 int save_dbl = debug_break_level;
2791 int save_ro = redir_off;
2793 debug_break_level = -1;
2797 typval_T * tvres = eval_expr(s, NULL);
2799 debug_break_level = save_dbl;
2800 redir_off = save_ro;
2807 CONVERT_FROM_UTF8_FREE(s);
2812 gui_update_cursor(FALSE, FALSE);
2815 if (tvres == NULL) {
2817 *errstr = @"Expression evaluation failed.";
2820 id res = vimToCocoa(tvres, 1);
2825 *errstr = @"Conversion to cocoa values failed.";
2834 @implementation NSString (VimStrings)
2836 + (id)stringWithVimString:(char_u *)s
2838 // This method ensures a non-nil string is returned. If 's' cannot be
2839 // converted to a utf-8 string it is assumed to be latin-1. If conversion
2840 // still fails an empty NSString is returned.
2841 NSString *string = nil;
2844 s = CONVERT_TO_UTF8(s);
2846 string = [NSString stringWithUTF8String:(char*)s];
2848 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2850 string = [NSString stringWithCString:(char*)s
2851 encoding:NSISOLatin1StringEncoding];
2854 CONVERT_TO_UTF8_FREE(s);
2858 return string != nil ? string : [NSString string];
2861 - (char_u *)vimStringSave
2863 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2866 s = CONVERT_FROM_UTF8(s);
2868 ret = vim_strsave(s);
2870 CONVERT_FROM_UTF8_FREE(s);
2876 @end // NSString (VimStrings)