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 s = [dialogReturn vimStringSave];
701 [dialogReturn release]; dialogReturn = nil;
703 @catch (NSException *e) {
704 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
710 - (oneway void)setDialogReturn:(in bycopy id)obj
712 // NOTE: This is called by
713 // - [MMVimController panelDidEnd:::], and
714 // - [MMVimController alertDidEnd:::],
715 // to indicate that a save/open panel or alert has finished.
717 // We want to distinguish between "no dialog return yet" and "dialog
718 // returned nothing". The former can be tested with dialogReturn == nil,
719 // the latter with dialogReturn == [NSNull null].
720 if (!obj) obj = [NSNull null];
722 if (obj != dialogReturn) {
723 [dialogReturn release];
724 dialogReturn = [obj retain];
728 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
733 [frontendProxy presentDialogWithAttributes:attr];
735 [self waitForDialogReturn];
737 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
738 && [dialogReturn count]) {
739 retval = [[dialogReturn objectAtIndex:0] intValue];
740 if (txtfield && [dialogReturn count] > 1) {
741 NSString *retString = [dialogReturn objectAtIndex:1];
742 char_u *ret = (char_u*)[retString UTF8String];
744 ret = CONVERT_FROM_UTF8(ret);
746 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
748 CONVERT_FROM_UTF8_FREE(ret);
753 [dialogReturn release]; dialogReturn = nil;
755 @catch (NSException *e) {
756 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
762 - (void)showToolbar:(int)enable flags:(int)flags
764 NSMutableData *data = [NSMutableData data];
766 [data appendBytes:&enable length:sizeof(int)];
767 [data appendBytes:&flags length:sizeof(int)];
769 [self queueMessage:ShowToolbarMsgID data:data];
772 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
774 NSMutableData *data = [NSMutableData data];
776 [data appendBytes:&ident length:sizeof(long)];
777 [data appendBytes:&type length:sizeof(int)];
779 [self queueMessage:CreateScrollbarMsgID data:data];
782 - (void)destroyScrollbarWithIdentifier:(long)ident
784 NSMutableData *data = [NSMutableData data];
785 [data appendBytes:&ident length:sizeof(long)];
787 [self queueMessage:DestroyScrollbarMsgID data:data];
790 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
792 NSMutableData *data = [NSMutableData data];
794 [data appendBytes:&ident length:sizeof(long)];
795 [data appendBytes:&visible length:sizeof(int)];
797 [self queueMessage:ShowScrollbarMsgID data:data];
800 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
802 NSMutableData *data = [NSMutableData data];
804 [data appendBytes:&ident length:sizeof(long)];
805 [data appendBytes:&pos length:sizeof(int)];
806 [data appendBytes:&len length:sizeof(int)];
808 [self queueMessage:SetScrollbarPositionMsgID data:data];
811 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
812 identifier:(long)ident
814 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
815 float prop = (float)size/(max+1);
816 if (fval < 0) fval = 0;
817 else if (fval > 1.0f) fval = 1.0f;
818 if (prop < 0) prop = 0;
819 else if (prop > 1.0f) prop = 1.0f;
821 NSMutableData *data = [NSMutableData data];
823 [data appendBytes:&ident length:sizeof(long)];
824 [data appendBytes:&fval length:sizeof(float)];
825 [data appendBytes:&prop length:sizeof(float)];
827 [self queueMessage:SetScrollbarThumbMsgID data:data];
830 - (void)setFont:(NSFont *)font
832 NSString *fontName = [font displayName];
833 float size = [font pointSize];
834 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
836 NSMutableData *data = [NSMutableData data];
838 [data appendBytes:&size length:sizeof(float)];
839 [data appendBytes:&len length:sizeof(int)];
840 [data appendBytes:[fontName UTF8String] length:len];
842 [self queueMessage:SetFontMsgID data:data];
846 - (void)setWideFont:(NSFont *)font
848 NSString *fontName = [font displayName];
849 float size = [font pointSize];
850 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
851 NSMutableData *data = [NSMutableData data];
853 [data appendBytes:&size length:sizeof(float)];
854 [data appendBytes:&len length:sizeof(int)];
856 [data appendBytes:[fontName UTF8String] length:len];
858 [self queueMessage:SetWideFontMsgID data:data];
861 - (void)executeActionWithName:(NSString *)name
863 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
866 NSMutableData *data = [NSMutableData data];
868 [data appendBytes:&len length:sizeof(int)];
869 [data appendBytes:[name UTF8String] length:len];
871 [self queueMessage:ExecuteActionMsgID data:data];
875 - (void)setMouseShape:(int)shape
877 NSMutableData *data = [NSMutableData data];
878 [data appendBytes:&shape length:sizeof(int)];
879 [self queueMessage:SetMouseShapeMsgID data:data];
882 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
884 // Vim specifies times in milliseconds, whereas Cocoa wants them in
886 blinkWaitInterval = .001f*wait;
887 blinkOnInterval = .001f*on;
888 blinkOffInterval = .001f*off;
894 [blinkTimer invalidate];
895 [blinkTimer release];
899 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
901 blinkState = MMBlinkStateOn;
903 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
905 selector:@selector(blinkTimerFired:)
906 userInfo:nil repeats:NO] retain];
907 gui_update_cursor(TRUE, FALSE);
908 [self flushQueue:YES];
914 if (MMBlinkStateOff == blinkState) {
915 gui_update_cursor(TRUE, FALSE);
916 [self flushQueue:YES];
919 blinkState = MMBlinkStateNone;
922 - (void)adjustLinespace:(int)linespace
924 NSMutableData *data = [NSMutableData data];
925 [data appendBytes:&linespace length:sizeof(int)];
926 [self queueMessage:AdjustLinespaceMsgID data:data];
931 [self queueMessage:ActivateMsgID data:nil];
934 - (void)setPreEditRow:(int)row column:(int)col
936 NSMutableData *data = [NSMutableData data];
937 [data appendBytes:&row length:sizeof(int)];
938 [data appendBytes:&col length:sizeof(int)];
939 [self queueMessage:SetPreEditPositionMsgID data:data];
942 - (int)lookupColorWithKey:(NSString *)key
944 if (!(key && [key length] > 0))
947 NSString *stripKey = [[[[key lowercaseString]
948 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
949 componentsSeparatedByString:@" "]
950 componentsJoinedByString:@""];
952 if (stripKey && [stripKey length] > 0) {
953 // First of all try to lookup key in the color dictionary; note that
954 // all keys in this dictionary are lowercase with no whitespace.
955 id obj = [colorDict objectForKey:stripKey];
956 if (obj) return [obj intValue];
958 // The key was not in the dictionary; is it perhaps of the form
960 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
961 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
962 [scanner setScanLocation:1];
964 if ([scanner scanHexInt:&hex]) {
969 // As a last resort, check if it is one of the system defined colors.
970 // The keys in this dictionary are also lowercase with no whitespace.
971 obj = [sysColorDict objectForKey:stripKey];
973 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
976 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
977 [col getRed:&r green:&g blue:&b alpha:&a];
978 return (((int)(r*255+.5f) & 0xff) << 16)
979 + (((int)(g*255+.5f) & 0xff) << 8)
980 + ((int)(b*255+.5f) & 0xff);
985 //NSLog(@"WARNING: No color with key %@ found.", stripKey);
989 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
991 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
994 while ((obj = [e nextObject])) {
995 if ([value isEqual:obj])
1002 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1004 NSMutableData *data = [NSMutableData data];
1005 [data appendBytes:&fuoptions length:sizeof(int)];
1007 [data appendBytes:&bg length:sizeof(int)];
1008 [self queueMessage:EnterFullscreenMsgID data:data];
1011 - (void)leaveFullscreen
1013 [self queueMessage:LeaveFullscreenMsgID data:nil];
1016 - (void)setFullscreenBackgroundColor:(int)color
1018 NSMutableData *data = [NSMutableData data];
1019 color = MM_COLOR(color);
1020 [data appendBytes:&color length:sizeof(int)];
1022 [self queueMessage:SetFullscreenColorMsgID data:data];
1025 - (void)setAntialias:(BOOL)antialias
1027 int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1029 [self queueMessage:msgid data:nil];
1032 - (void)updateModifiedFlag
1034 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1036 int msgid = [self checkForModifiedBuffers]
1037 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1039 [self queueMessage:msgid data:nil];
1042 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1044 // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1045 // queue is processed since that only happens in waitForInput: (and Vim
1046 // regularly checks for Ctrl-C in between waiting for input).
1048 BOOL interrupt = NO;
1049 if (msgid == InterruptMsgID) {
1051 } else if (InsertTextMsgID == msgid && data != nil && [data length] == 1) {
1052 char_u *str = (char_u*)[data bytes];
1053 if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1054 (str[0] == intr_char && intr_char != Ctrl_C))
1060 [inputQueue removeAllObjects];
1064 // Remove all previous instances of this message from the input queue, else
1065 // the input queue may fill up as a result of Vim not being able to keep up
1066 // with the speed at which new messages are received. This avoids annoying
1067 // situations such as when the keyboard repeat rate is higher than what Vim
1068 // can cope with (which would cause a 'stutter' when scrolling by holding
1069 // down 'j' and then when 'j' was released the screen kept scrolling for a
1072 int i, count = [inputQueue count];
1073 for (i = 1; i < count; i+=2) {
1074 if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1075 [inputQueue removeObjectAtIndex:i];
1076 [inputQueue removeObjectAtIndex:i-1];
1081 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1082 [inputQueue addObject:(data ? (id)data : [NSNull null])];
1085 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1087 // This is just a convenience method that allows the frontend to delay
1088 // sending messages.
1089 int i, count = [messages count];
1090 for (i = 1; i < count; i+=2)
1091 [self processInput:[[messages objectAtIndex:i-1] intValue]
1092 data:[messages objectAtIndex:i]];
1095 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1096 errorString:(out bycopy NSString **)errstr
1098 return evalExprCocoa(expr, errstr);
1102 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1104 NSString *eval = nil;
1105 char_u *s = (char_u*)[expr UTF8String];
1108 s = CONVERT_FROM_UTF8(s);
1111 char_u *res = eval_client_expr_to_string(s);
1114 CONVERT_FROM_UTF8_FREE(s);
1120 s = CONVERT_TO_UTF8(s);
1122 eval = [NSString stringWithUTF8String:(char*)s];
1124 CONVERT_TO_UTF8_FREE(s);
1132 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1134 // TODO: This method should share code with clip_mch_request_selection().
1136 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1137 // If there is no pasteboard, return YES to indicate that there is text
1142 clip_copy_selection();
1144 // Get the text to put on the pasteboard.
1145 long_u llen = 0; char_u *str = 0;
1146 int type = clip_convert_selection(&str, &llen, &clip_star);
1150 // TODO: Avoid overflow.
1151 int len = (int)llen;
1153 if (output_conv.vc_type != CONV_NONE) {
1154 char_u *conv_str = string_convert(&output_conv, str, &len);
1162 NSString *string = [[NSString alloc]
1163 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1165 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1166 [pboard declareTypes:types owner:nil];
1167 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1178 - (oneway void)addReply:(in bycopy NSString *)reply
1179 server:(in byref id <MMVimServerProtocol>)server
1181 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1183 // Replies might come at any time and in any order so we keep them in an
1184 // array inside a dictionary with the send port used as key.
1186 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1187 // HACK! Assume connection uses mach ports.
1188 int port = [(NSMachPort*)[conn sendPort] machPort];
1189 NSNumber *key = [NSNumber numberWithInt:port];
1191 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1193 replies = [NSMutableArray array];
1194 [serverReplyDict setObject:replies forKey:key];
1197 [replies addObject:reply];
1200 - (void)addInput:(in bycopy NSString *)input
1201 client:(in byref id <MMVimClientProtocol>)client
1203 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1205 [self addInput:input];
1206 [self addClient:(id)client];
1209 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1210 client:(in byref id <MMVimClientProtocol>)client
1212 [self addClient:(id)client];
1213 return [self evaluateExpression:expr];
1216 - (void)registerServerWithName:(NSString *)name
1218 NSString *svrName = name;
1219 NSConnection *svrConn = [NSConnection defaultConnection];
1222 for (i = 0; i < MMServerMax; ++i) {
1223 NSString *connName = [self connectionNameFromServerName:svrName];
1225 if ([svrConn registerName:connName]) {
1226 //NSLog(@"Registered server with name: %@", svrName);
1228 // TODO: Set request/reply time-outs to something else?
1230 // Don't wait for requests (time-out means that the message is
1232 [svrConn setRequestTimeout:0];
1233 //[svrConn setReplyTimeout:MMReplyTimeout];
1234 [svrConn setRootObject:self];
1236 // NOTE: 'serverName' is a global variable
1237 serverName = [svrName vimStringSave];
1239 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1242 need_maketitle = TRUE;
1244 [self queueMessage:SetServerNameMsgID data:
1245 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1249 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1253 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1254 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1257 // NOTE: If 'name' equals 'serverName' then the request is local (client
1258 // and server are the same). This case is not handled separately, so a
1259 // connection will be set up anyway (this simplifies the code).
1261 NSConnection *conn = [self connectionForServerName:name];
1264 char_u *s = (char_u*)[name UTF8String];
1266 s = CONVERT_FROM_UTF8(s);
1268 EMSG2(_(e_noserver), s);
1270 CONVERT_FROM_UTF8_FREE(s);
1277 // HACK! Assume connection uses mach ports.
1278 *port = [(NSMachPort*)[conn sendPort] machPort];
1281 id proxy = [conn rootProxy];
1282 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1286 NSString *eval = [proxy evaluateExpression:string client:self];
1289 *reply = [eval vimStringSave];
1291 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1298 [proxy addInput:string client:self];
1301 @catch (NSException *e) {
1302 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1309 - (NSArray *)serverList
1311 NSArray *list = nil;
1313 if ([self connection]) {
1314 id proxy = [connection rootProxy];
1315 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1318 list = [proxy serverList];
1320 @catch (NSException *e) {
1321 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1324 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1330 - (NSString *)peekForReplyOnPort:(int)port
1332 //NSLog(@"%s%d", _cmd, port);
1334 NSNumber *key = [NSNumber numberWithInt:port];
1335 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1336 if (replies && [replies count]) {
1337 //NSLog(@" %d replies, topmost is: %@", [replies count],
1338 // [replies objectAtIndex:0]);
1339 return [replies objectAtIndex:0];
1342 //NSLog(@" No replies");
1346 - (NSString *)waitForReplyOnPort:(int)port
1348 //NSLog(@"%s%d", _cmd, port);
1350 NSConnection *conn = [self connectionForServerPort:port];
1354 NSNumber *key = [NSNumber numberWithInt:port];
1355 NSMutableArray *replies = nil;
1356 NSString *reply = nil;
1358 // Wait for reply as long as the connection to the server is valid (unless
1359 // user interrupts wait with Ctrl-C).
1360 while (!got_int && [conn isValid] &&
1361 !(replies = [serverReplyDict objectForKey:key])) {
1362 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1363 beforeDate:[NSDate distantFuture]];
1367 if ([replies count] > 0) {
1368 reply = [[replies objectAtIndex:0] retain];
1369 //NSLog(@" Got reply: %@", reply);
1370 [replies removeObjectAtIndex:0];
1371 [reply autorelease];
1374 if ([replies count] == 0)
1375 [serverReplyDict removeObjectForKey:key];
1381 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1383 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1386 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1387 [client addReply:reply server:self];
1390 @catch (NSException *e) {
1391 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1394 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1405 - (void)setWaitForAck:(BOOL)yn
1410 - (void)waitForConnectionAcknowledgement
1412 if (!waitForAck) return;
1414 while (waitForAck && !got_int && [connection isValid] && !isTerminating) {
1415 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1416 beforeDate:[NSDate distantFuture]];
1417 //NSLog(@" waitForAck=%d got_int=%d isTerminating=%d isValid=%d",
1418 // waitForAck, got_int, isTerminating, [connection isValid]);
1422 // Never received a connection acknowledgement, so die.
1423 [[NSNotificationCenter defaultCenter] removeObserver:self];
1424 [frontendProxy release]; frontendProxy = nil;
1426 // NOTE: We intentionally do not call mch_exit() since this in turn
1427 // will lead to -[MMBackend exit] getting called which we want to
1429 usleep(MMExitProcessDelay);
1433 [self processInputQueue];
1436 - (oneway void)acknowledgeConnection
1438 //NSLog(@"%s", _cmd);
1446 @implementation MMBackend (Private)
1448 - (void)waitForDialogReturn
1450 // Keep processing the run loop until a dialog returns. To avoid getting
1451 // stuck in an endless loop (could happen if the setDialogReturn: message
1452 // was lost) we also do some paranoia checks.
1454 // Note that in Cocoa the user can still resize windows and select menu
1455 // items while a sheet is being displayed, so we can't just wait for the
1456 // first message to arrive and assume that is the setDialogReturn: call.
1458 while (nil == dialogReturn && !got_int && [connection isValid]
1460 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1461 beforeDate:[NSDate distantFuture]];
1463 // Search for any resize messages on the input queue. All other messages
1464 // on the input queue are dropped. The reason why we single out resize
1465 // messages is because the user may have resized the window while a sheet
1467 int i, count = [inputQueue count];
1469 id textDimData = nil;
1471 for (i = count-2; i >= 0; i -= 2) {
1472 int msgid = [[inputQueue objectAtIndex:i] intValue];
1473 if (SetTextDimensionsMsgID == msgid) {
1474 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1480 [inputQueue removeAllObjects];
1483 [inputQueue addObject:
1484 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1485 [inputQueue addObject:textDimData];
1486 [textDimData release];
1491 - (void)insertVimStateMessage
1493 // NOTE: This is the place to add Vim state that needs to be accessed from
1494 // MacVim. Do not add state that could potentially require lots of memory
1495 // since this message gets sent each time the output queue is forcibly
1496 // flushed (e.g. storing the currently selected text would be a bad idea).
1497 // We take this approach of "pushing" the state to MacVim to avoid having
1498 // to make synchronous calls from MacVim to Vim in order to get state.
1500 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1501 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1502 [NSNumber numberWithInt:p_mh], @"p_mh",
1503 [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1506 // Put the state before all other messages.
1507 int msgid = SetVimStateMsgID;
1508 [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1509 [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1513 - (void)processInputQueue
1515 if ([inputQueue count] == 0) return;
1517 // NOTE: One of the input events may cause this method to be called
1518 // recursively, so copy the input queue to a local variable and clear the
1519 // queue before starting to process input events (otherwise we could get
1520 // stuck in an endless loop).
1521 NSArray *q = [inputQueue copy];
1522 unsigned i, count = [q count];
1524 [inputQueue removeAllObjects];
1526 for (i = 1; i < count; i+=2) {
1527 int msgid = [[q objectAtIndex:i-1] intValue];
1528 id data = [q objectAtIndex:i];
1529 if ([data isEqual:[NSNull null]])
1532 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1533 [self handleInputEvent:msgid data:data];
1537 //NSLog(@"Clear input event queue");
1540 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1542 if (InsertTextMsgID == msgid) {
1543 [self handleInsertText:data];
1544 } else if (KeyDownMsgID == msgid || CmdKeyMsgID == msgid) {
1546 const void *bytes = [data bytes];
1547 int mods = *((int*)bytes); bytes += sizeof(int);
1548 int len = *((int*)bytes); bytes += sizeof(int);
1549 NSString *key = [[NSString alloc] initWithBytes:bytes length:len
1550 encoding:NSUTF8StringEncoding];
1551 mods = eventModifierFlagsToVimModMask(mods);
1553 [self handleKeyDown:key modifiers:mods];
1556 } else if (ScrollWheelMsgID == msgid) {
1558 const void *bytes = [data bytes];
1560 int row = *((int*)bytes); bytes += sizeof(int);
1561 int col = *((int*)bytes); bytes += sizeof(int);
1562 int flags = *((int*)bytes); bytes += sizeof(int);
1563 float dy = *((float*)bytes); bytes += sizeof(float);
1565 int button = MOUSE_5;
1566 if (dy > 0) button = MOUSE_4;
1568 flags = eventModifierFlagsToVimMouseModMask(flags);
1570 int numLines = (int)round(dy);
1571 if (numLines < 0) numLines = -numLines;
1572 if (numLines == 0) numLines = 1;
1574 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1575 gui.scroll_wheel_force = numLines;
1578 gui_send_mouse_event(button, col, row, NO, flags);
1579 } else if (MouseDownMsgID == msgid) {
1581 const void *bytes = [data bytes];
1583 int row = *((int*)bytes); bytes += sizeof(int);
1584 int col = *((int*)bytes); bytes += sizeof(int);
1585 int button = *((int*)bytes); bytes += sizeof(int);
1586 int flags = *((int*)bytes); bytes += sizeof(int);
1587 int count = *((int*)bytes); bytes += sizeof(int);
1589 button = eventButtonNumberToVimMouseButton(button);
1591 flags = eventModifierFlagsToVimMouseModMask(flags);
1592 gui_send_mouse_event(button, col, row, count>1, flags);
1594 } else if (MouseUpMsgID == msgid) {
1596 const void *bytes = [data bytes];
1598 int row = *((int*)bytes); bytes += sizeof(int);
1599 int col = *((int*)bytes); bytes += sizeof(int);
1600 int flags = *((int*)bytes); bytes += sizeof(int);
1602 flags = eventModifierFlagsToVimMouseModMask(flags);
1604 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1605 } else if (MouseDraggedMsgID == msgid) {
1607 const void *bytes = [data bytes];
1609 int row = *((int*)bytes); bytes += sizeof(int);
1610 int col = *((int*)bytes); bytes += sizeof(int);
1611 int flags = *((int*)bytes); bytes += sizeof(int);
1613 flags = eventModifierFlagsToVimMouseModMask(flags);
1615 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1616 } else if (MouseMovedMsgID == msgid) {
1617 const void *bytes = [data bytes];
1618 int row = *((int*)bytes); bytes += sizeof(int);
1619 int col = *((int*)bytes); bytes += sizeof(int);
1621 gui_mouse_moved(col, row);
1622 } else if (AddInputMsgID == msgid) {
1623 NSString *string = [[NSString alloc] initWithData:data
1624 encoding:NSUTF8StringEncoding];
1626 [self addInput:string];
1629 } else if (TerminateNowMsgID == msgid) {
1630 isTerminating = YES;
1631 } else if (SelectTabMsgID == msgid) {
1633 const void *bytes = [data bytes];
1634 int idx = *((int*)bytes) + 1;
1635 //NSLog(@"Selecting tab %d", idx);
1636 send_tabline_event(idx);
1637 } else if (CloseTabMsgID == msgid) {
1639 const void *bytes = [data bytes];
1640 int idx = *((int*)bytes) + 1;
1641 //NSLog(@"Closing tab %d", idx);
1642 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1643 } else if (AddNewTabMsgID == msgid) {
1644 //NSLog(@"Adding new tab");
1645 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1646 } else if (DraggedTabMsgID == msgid) {
1648 const void *bytes = [data bytes];
1649 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1651 int idx = *((int*)bytes);
1654 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1655 || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1657 const void *bytes = [data bytes];
1659 if (SetTextColumnsMsgID != msgid) {
1660 rows = *((int*)bytes); bytes += sizeof(int);
1663 if (SetTextRowsMsgID != msgid) {
1664 cols = *((int*)bytes); bytes += sizeof(int);
1668 if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1669 int dim[2] = { rows, cols };
1670 d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1671 msgid = SetTextDimensionsMsgID;
1674 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1675 // gui_resize_shell(), so we have to manually set the rows and columns
1676 // here. (MacVim doesn't change the rows and columns to avoid
1677 // inconsistent states between Vim and MacVim.)
1678 [self queueMessage:msgid data:d];
1680 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1681 gui_resize_shell(cols, rows);
1682 } else if (ExecuteMenuMsgID == msgid) {
1683 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1685 NSArray *desc = [attrs objectForKey:@"descriptor"];
1686 vimmenu_T *menu = menu_for_descriptor(desc);
1690 } else if (ToggleToolbarMsgID == msgid) {
1691 [self handleToggleToolbar];
1692 } else if (ScrollbarEventMsgID == msgid) {
1693 [self handleScrollbarEvent:data];
1694 } else if (SetFontMsgID == msgid) {
1695 [self handleSetFont:data];
1696 } else if (VimShouldCloseMsgID == msgid) {
1698 } else if (DropFilesMsgID == msgid) {
1699 [self handleDropFiles:data];
1700 } else if (DropStringMsgID == msgid) {
1701 [self handleDropString:data];
1702 } else if (GotFocusMsgID == msgid) {
1704 [self focusChange:YES];
1705 } else if (LostFocusMsgID == msgid) {
1707 [self focusChange:NO];
1708 } else if (SetMouseShapeMsgID == msgid) {
1709 const void *bytes = [data bytes];
1710 int shape = *((int*)bytes); bytes += sizeof(int);
1711 update_mouseshape(shape);
1712 } else if (XcodeModMsgID == msgid) {
1713 [self handleXcodeMod:data];
1714 } else if (OpenWithArgumentsMsgID == msgid) {
1715 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1717 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1721 + (NSDictionary *)specialKeys
1723 static NSDictionary *specialKeys = nil;
1726 NSBundle *mainBundle = [NSBundle mainBundle];
1727 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1729 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1735 - (void)handleInsertText:(NSData *)data
1739 NSString *key = [[NSString alloc] initWithData:data
1740 encoding:NSUTF8StringEncoding];
1741 char_u *str = (char_u*)[key UTF8String];
1742 int i, len = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1745 char_u *conv_str = NULL;
1746 if (input_conv.vc_type != CONV_NONE) {
1747 conv_str = string_convert(&input_conv, str, &len);
1753 for (i = 0; i < len; ++i) {
1754 add_to_input_buf(str+i, 1);
1755 if (CSI == str[i]) {
1756 // NOTE: If the converted string contains the byte CSI, then it
1757 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1759 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1760 add_to_input_buf(extra, 2);
1771 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1775 char_u *chars = (char_u*)[key UTF8String];
1777 char_u *conv_str = NULL;
1779 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1781 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1782 // that new keys can easily be added.
1783 NSString *specialString = [[MMBackend specialKeys]
1785 if (specialString && [specialString length] > 1) {
1786 //NSLog(@"special key: %@", specialString);
1787 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1788 [specialString characterAtIndex:1]);
1790 ikey = simplify_key(ikey, &mods);
1795 special[1] = K_SECOND(ikey);
1796 special[2] = K_THIRD(ikey);
1800 } else if (1 == length && TAB == chars[0]) {
1801 // Tab is a trouble child:
1802 // - <Tab> is added to the input buffer as is
1803 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1804 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1805 // to be converted to utf-8
1806 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1807 // - <C-Tab> is reserved by Mac OS X
1808 // - <D-Tab> is reserved by Mac OS X
1813 if (mods & MOD_MASK_SHIFT) {
1814 mods &= ~MOD_MASK_SHIFT;
1816 special[1] = K_SECOND(K_S_TAB);
1817 special[2] = K_THIRD(K_S_TAB);
1819 } else if (mods & MOD_MASK_ALT) {
1820 int mtab = 0x80 | TAB;
1824 special[0] = (mtab >> 6) + 0xc0;
1825 special[1] = mtab & 0xbf;
1833 mods &= ~MOD_MASK_ALT;
1835 } else if (length > 0) {
1836 unichar c = [key characterAtIndex:0];
1838 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1839 // [key characterAtIndex:0], mods);
1841 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1842 // cleared since they are already added to the key by the AppKit.
1843 // Unfortunately, the only way to deal with when to clear the modifiers
1844 // or not seems to be to have hard-wired rules like this.
1845 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1846 || 0x9 == c || 0xd == c || ESC == c) ) {
1847 mods &= ~MOD_MASK_SHIFT;
1848 mods &= ~MOD_MASK_CTRL;
1849 //NSLog(@"clear shift ctrl");
1852 // HACK! All Option+key presses go via 'insert text' messages, except
1853 // for <M-Space>. If the Alt flag is not cleared for <M-Space> it does
1854 // not work to map to it.
1855 if (0xa0 == c && !(mods & MOD_MASK_CMD)) {
1856 //NSLog(@"clear alt");
1857 mods &= ~MOD_MASK_ALT;
1861 if (input_conv.vc_type != CONV_NONE) {
1862 conv_str = string_convert(&input_conv, chars, &length);
1869 if (chars && length > 0) {
1871 //NSLog(@"adding mods: %d", mods);
1873 modChars[1] = KS_MODIFIER;
1875 add_to_input_buf(modChars, 3);
1878 //NSLog(@"add to input buf: 0x%x", chars[0]);
1879 // TODO: Check for CSI bytes?
1880 add_to_input_buf(chars, length);
1889 - (void)queueMessage:(int)msgid data:(NSData *)data
1891 //if (msgid != EnableMenuItemMsgID)
1892 // NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1894 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1896 [outputQueue addObject:data];
1898 [outputQueue addObject:[NSData data]];
1901 - (void)connectionDidDie:(NSNotification *)notification
1903 // If the main connection to MacVim is lost this means that MacVim was
1904 // either quit (by the user chosing Quit on the MacVim menu), or it has
1905 // crashed. In the former case the flag 'isTerminating' is set and we then
1906 // quit cleanly; in the latter case we make sure the swap files are left
1909 // NOTE: This is not called if a Vim controller invalidates its connection.
1911 //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1915 getout_preserve_modified(1);
1918 - (void)blinkTimerFired:(NSTimer *)timer
1920 NSTimeInterval timeInterval = 0;
1922 [blinkTimer release];
1925 if (MMBlinkStateOn == blinkState) {
1926 gui_undraw_cursor();
1927 blinkState = MMBlinkStateOff;
1928 timeInterval = blinkOffInterval;
1929 } else if (MMBlinkStateOff == blinkState) {
1930 gui_update_cursor(TRUE, FALSE);
1931 blinkState = MMBlinkStateOn;
1932 timeInterval = blinkOnInterval;
1935 if (timeInterval > 0) {
1937 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
1938 selector:@selector(blinkTimerFired:)
1939 userInfo:nil repeats:NO] retain];
1940 [self flushQueue:YES];
1944 - (void)focusChange:(BOOL)on
1946 gui_focus_change(on);
1949 - (void)handleToggleToolbar
1951 // If 'go' contains 'T', then remove it, else add it.
1953 char_u go[sizeof(GO_ALL)+2];
1958 p = vim_strchr(go, GO_TOOLBAR);
1962 char_u *end = go + len;
1968 go[len] = GO_TOOLBAR;
1972 set_option_value((char_u*)"guioptions", 0, go, 0);
1974 [self redrawScreen];
1977 - (void)handleScrollbarEvent:(NSData *)data
1981 const void *bytes = [data bytes];
1982 long ident = *((long*)bytes); bytes += sizeof(long);
1983 int hitPart = *((int*)bytes); bytes += sizeof(int);
1984 float fval = *((float*)bytes); bytes += sizeof(float);
1985 scrollbar_T *sb = gui_find_scrollbar(ident);
1988 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
1989 long value = sb_info->value;
1990 long size = sb_info->size;
1991 long max = sb_info->max;
1992 BOOL isStillDragging = NO;
1993 BOOL updateKnob = YES;
1996 case NSScrollerDecrementPage:
1997 value -= (size > 2 ? size - 2 : 1);
1999 case NSScrollerIncrementPage:
2000 value += (size > 2 ? size - 2 : 1);
2002 case NSScrollerDecrementLine:
2005 case NSScrollerIncrementLine:
2008 case NSScrollerKnob:
2009 isStillDragging = YES;
2011 case NSScrollerKnobSlot:
2012 value = (long)(fval * (max - size + 1));
2019 //NSLog(@"value %d -> %d", sb_info->value, value);
2020 gui_drag_scrollbar(sb, value, isStillDragging);
2023 // Dragging the knob or option+clicking automatically updates
2024 // the knob position (on the actual NSScroller), so we only
2025 // need to set the knob position in the other cases.
2027 // Update both the left&right vertical scrollbars.
2028 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2029 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2030 [self setScrollbarThumbValue:value size:size max:max
2031 identifier:identLeft];
2032 [self setScrollbarThumbValue:value size:size max:max
2033 identifier:identRight];
2035 // Update the horizontal scrollbar.
2036 [self setScrollbarThumbValue:value size:size max:max
2043 - (void)handleSetFont:(NSData *)data
2047 const void *bytes = [data bytes];
2048 float pointSize = *((float*)bytes); bytes += sizeof(float);
2049 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2050 bytes += sizeof(unsigned); // len not used
2052 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2053 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2054 char_u *s = (char_u*)[name UTF8String];
2057 s = CONVERT_FROM_UTF8(s);
2060 set_option_value((char_u*)"guifont", 0, s, 0);
2063 CONVERT_FROM_UTF8_FREE(s);
2066 [self redrawScreen];
2069 - (void)handleDropFiles:(NSData *)data
2071 // TODO: Get rid of this method; instead use Vim script directly. At the
2072 // moment I know how to do this to open files in tabs, but I'm not sure how
2073 // to add the filenames to the command line when in command line mode.
2077 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2080 id obj = [args objectForKey:@"forceOpen"];
2081 BOOL forceOpen = YES;
2083 forceOpen = [obj boolValue];
2085 NSArray *filenames = [args objectForKey:@"filenames"];
2086 if (!(filenames && [filenames count] > 0)) return;
2089 if (!forceOpen && (State & CMDLINE)) {
2090 // HACK! If Vim is in command line mode then the files names
2091 // should be added to the command line, instead of opening the
2092 // files in tabs (unless forceOpen is set). This is taken care of by
2093 // gui_handle_drop().
2094 int n = [filenames count];
2095 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2098 for (i = 0; i < n; ++i)
2099 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2101 // NOTE! This function will free 'fnames'.
2102 // HACK! It is assumed that the 'x' and 'y' arguments are
2103 // unused when in command line mode.
2104 gui_handle_drop(0, 0, 0, fnames, n);
2109 [self handleOpenWithArguments:args];
2113 - (void)handleDropString:(NSData *)data
2118 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2119 const void *bytes = [data bytes];
2120 int len = *((int*)bytes); bytes += sizeof(int);
2121 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2123 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2124 NSRange range = { 0, [string length] };
2125 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2126 withString:@"\x0a" options:0
2129 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2130 options:0 range:range];
2133 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2134 char_u *s = (char_u*)[string UTF8String];
2136 if (input_conv.vc_type != CONV_NONE)
2137 s = string_convert(&input_conv, s, &len);
2139 dnd_yank_drag_data(s, len);
2141 if (input_conv.vc_type != CONV_NONE)
2144 add_to_input_buf(dropkey, sizeof(dropkey));
2148 - (void)startOdbEditWithArguments:(NSDictionary *)args
2150 #ifdef FEAT_ODB_EDITOR
2151 id obj = [args objectForKey:@"remoteID"];
2154 OSType serverID = [obj unsignedIntValue];
2155 NSString *remotePath = [args objectForKey:@"remotePath"];
2157 NSAppleEventDescriptor *token = nil;
2158 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2159 obj = [args objectForKey:@"remoteTokenDescType"];
2160 if (tokenData && obj) {
2161 DescType tokenType = [obj unsignedLongValue];
2162 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2166 NSArray *filenames = [args objectForKey:@"filenames"];
2167 unsigned i, numFiles = [filenames count];
2168 for (i = 0; i < numFiles; ++i) {
2169 NSString *filename = [filenames objectAtIndex:i];
2170 char_u *s = [filename vimStringSave];
2171 buf_T *buf = buflist_findname(s);
2175 if (buf->b_odb_token) {
2176 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2177 buf->b_odb_token = NULL;
2180 if (buf->b_odb_fname) {
2181 vim_free(buf->b_odb_fname);
2182 buf->b_odb_fname = NULL;
2185 buf->b_odb_server_id = serverID;
2188 buf->b_odb_token = [token retain];
2190 buf->b_odb_fname = [remotePath vimStringSave];
2192 NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2196 #endif // FEAT_ODB_EDITOR
2199 - (void)handleXcodeMod:(NSData *)data
2202 const void *bytes = [data bytes];
2203 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2204 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2208 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2209 descriptorWithDescriptorType:type
2215 - (void)handleOpenWithArguments:(NSDictionary *)args
2217 // ARGUMENT: DESCRIPTION:
2218 // -------------------------------------------------------------
2219 // filenames list of filenames
2220 // dontOpen don't open files specified in above argument
2221 // layout which layout to use to open files
2222 // selectionRange range of lines to select
2223 // searchText string to search for
2224 // cursorLine line to position the cursor on
2225 // cursorColumn column to position the cursor on
2226 // (only valid when "cursorLine" is set)
2227 // remoteID ODB parameter
2228 // remotePath ODB parameter
2229 // remoteTokenDescType ODB parameter
2230 // remoteTokenData ODB parameter
2232 //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2234 NSArray *filenames = [args objectForKey:@"filenames"];
2235 int i, numFiles = filenames ? [filenames count] : 0;
2236 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2237 int layout = [[args objectForKey:@"layout"] intValue];
2239 // Change to directory of first file to open if this is an "unused" editor
2240 // (but do not do this if editing remotely).
2241 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2242 && (starting || [self unusedEditor]) ) {
2243 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2249 // When Vim is starting we simply add the files to be opened to the
2250 // global arglist and Vim will take care of opening them for us.
2251 if (openFiles && numFiles > 0) {
2252 for (i = 0; i < numFiles; i++) {
2253 NSString *fname = [filenames objectAtIndex:i];
2256 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2257 || (p = [fname vimStringSave]) == NULL)
2258 exit(2); // See comment in -[MMBackend exit]
2260 alist_add(&global_alist, p, 2);
2263 // Vim will take care of arranging the files added to the arglist
2264 // in windows or tabs; all we must do is to specify which layout to
2266 initialWindowLayout = layout;
2269 // When Vim is already open we resort to some trickery to open the
2270 // files with the specified layout.
2272 // TODO: Figure out a better way to handle this?
2273 if (openFiles && numFiles > 0) {
2274 BOOL oneWindowInTab = topframe ? YES
2275 : (topframe->fr_layout == FR_LEAF);
2276 BOOL bufChanged = NO;
2277 BOOL bufHasFilename = NO;
2279 bufChanged = curbufIsChanged();
2280 bufHasFilename = curbuf->b_ffname != NULL;
2283 // Temporarily disable flushing since the following code may
2284 // potentially cause multiple redraws.
2285 flushDisabled = YES;
2287 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2288 if (WIN_TABS == layout && !onlyOneTab) {
2289 // By going to the last tabpage we ensure that the new tabs
2290 // will appear last (if this call is left out, the taborder
2295 // Make sure we're in normal mode first.
2296 [self addInput:@"<C-\\><C-N>"];
2299 // With "split layout" we open a new tab before opening
2300 // multiple files if the current tab has more than one window
2301 // or if there is exactly one window but whose buffer has a
2302 // filename. (The :drop command ensures modified buffers get
2303 // their own window.)
2304 if ((WIN_HOR == layout || WIN_VER == layout) &&
2305 (!oneWindowInTab || bufHasFilename))
2306 [self addInput:@":tabnew<CR>"];
2308 // The files are opened by constructing a ":drop ..." command
2309 // and executing it.
2310 NSMutableString *cmd = (WIN_TABS == layout)
2311 ? [NSMutableString stringWithString:@":tab drop"]
2312 : [NSMutableString stringWithString:@":drop"];
2314 for (i = 0; i < numFiles; ++i) {
2315 NSString *file = [filenames objectAtIndex:i];
2316 file = [file stringByEscapingSpecialFilenameCharacters];
2317 [cmd appendString:@" "];
2318 [cmd appendString:file];
2321 // Temporarily clear 'suffixes' so that the files are opened in
2322 // the same order as they appear in the "filenames" array.
2323 [self addInput:@":let t:mvim_oldsu=&su|set su=<CR>"];
2325 [self addInput:cmd];
2327 // Split the view into multiple windows if requested.
2328 if (WIN_HOR == layout)
2329 [self addInput:@"|sall"];
2330 else if (WIN_VER == layout)
2331 [self addInput:@"|vert sall"];
2333 // Restore the old value of 'suffixes'.
2334 [self addInput:@"|let &su=t:mvim_oldsu|unlet t:mvim_oldsu"];
2336 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2337 [self addInput:@"|redr|f<CR>"];
2339 // When opening one file we try to reuse the current window,
2340 // but not if its buffer is modified or has a filename.
2341 // However, the 'arglist' layout always opens the file in the
2343 NSString *file = [[filenames lastObject]
2344 stringByEscapingSpecialFilenameCharacters];
2346 if (WIN_HOR == layout) {
2347 if (!(bufHasFilename || bufChanged))
2348 cmd = [NSString stringWithFormat:@":e %@", file];
2350 cmd = [NSString stringWithFormat:@":sp %@", file];
2351 } else if (WIN_VER == layout) {
2352 if (!(bufHasFilename || bufChanged))
2353 cmd = [NSString stringWithFormat:@":e %@", file];
2355 cmd = [NSString stringWithFormat:@":vsp %@", file];
2356 } else if (WIN_TABS == layout) {
2357 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2358 cmd = [NSString stringWithFormat:@":e %@", file];
2360 cmd = [NSString stringWithFormat:@":tabe %@", file];
2362 // (The :drop command will split if there is a modified
2364 cmd = [NSString stringWithFormat:@":drop %@", file];
2367 [self addInput:cmd];
2369 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2370 [self addInput:@"|redr|f<CR>"];
2373 // Force screen redraw (does it have to be this complicated?).
2374 // (This code was taken from the end of gui_handle_drop().)
2375 update_screen(NOT_VALID);
2378 gui_update_cursor(FALSE, FALSE);
2385 if ([args objectForKey:@"remoteID"]) {
2386 // NOTE: We have to delay processing any ODB related arguments since
2387 // the file(s) may not be opened until the input buffer is processed.
2388 [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2393 NSString *lineString = [args objectForKey:@"cursorLine"];
2394 if (lineString && [lineString intValue] > 0) {
2395 NSString *columnString = [args objectForKey:@"cursorColumn"];
2396 if (!(columnString && [columnString intValue] > 0))
2397 columnString = @"1";
2399 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2400 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2401 [self addInput:cmd];
2404 NSString *rangeString = [args objectForKey:@"selectionRange"];
2406 // Build a command line string that will select the given range of
2407 // lines. If range.length == 0, then position the cursor on the given
2408 // line but do not select.
2409 NSRange range = NSRangeFromString(rangeString);
2411 if (range.length > 0) {
2412 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2413 NSMaxRange(range), range.location];
2415 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2419 [self addInput:cmd];
2422 NSString *searchText = [args objectForKey:@"searchText"];
2424 // TODO: Searching is an exclusive motion, so if the pattern would
2425 // match on row 0 column 0 then this pattern will miss that match.
2426 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@<CR>",
2431 - (BOOL)checkForModifiedBuffers
2434 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2435 if (bufIsChanged(buf)) {
2443 - (void)addInput:(NSString *)input
2445 char_u *s = (char_u*)[input UTF8String];
2448 s = CONVERT_FROM_UTF8(s);
2451 server_to_input_buf(s);
2454 CONVERT_FROM_UTF8_FREE(s);
2458 - (BOOL)unusedEditor
2460 BOOL oneWindowInTab = topframe ? YES
2461 : (topframe->fr_layout == FR_LEAF);
2462 BOOL bufChanged = NO;
2463 BOOL bufHasFilename = NO;
2465 bufChanged = curbufIsChanged();
2466 bufHasFilename = curbuf->b_ffname != NULL;
2469 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2471 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2474 - (void)redrawScreen
2476 // Force screen redraw (does it have to be this complicated?).
2477 redraw_all_later(CLEAR);
2478 update_screen(NOT_VALID);
2481 gui_update_cursor(FALSE, FALSE);
2483 // HACK! The cursor is not put back at the command line by the above
2484 // "redraw commands". The following test seems to do the trick though.
2485 if (State & CMDLINE)
2489 @end // MMBackend (Private)
2494 @implementation MMBackend (ClientServer)
2496 - (NSString *)connectionNameFromServerName:(NSString *)name
2498 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2500 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2504 - (NSConnection *)connectionForServerName:(NSString *)name
2506 // TODO: Try 'name%d' if 'name' fails.
2507 NSString *connName = [self connectionNameFromServerName:name];
2508 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2511 svrConn = [NSConnection connectionWithRegisteredName:connName
2513 // Try alternate server...
2514 if (!svrConn && alternateServerName) {
2515 //NSLog(@" trying to connect to alternate server: %@",
2516 // alternateServerName);
2517 connName = [self connectionNameFromServerName:alternateServerName];
2518 svrConn = [NSConnection connectionWithRegisteredName:connName
2522 // Try looking for alternate servers...
2524 //NSLog(@" looking for alternate servers...");
2525 NSString *alt = [self alternateServerNameForName:name];
2526 if (alt != alternateServerName) {
2527 //NSLog(@" found alternate server: %@", string);
2528 [alternateServerName release];
2529 alternateServerName = [alt copy];
2533 // Try alternate server again...
2534 if (!svrConn && alternateServerName) {
2535 //NSLog(@" trying to connect to alternate server: %@",
2536 // alternateServerName);
2537 connName = [self connectionNameFromServerName:alternateServerName];
2538 svrConn = [NSConnection connectionWithRegisteredName:connName
2543 [connectionNameDict setObject:svrConn forKey:connName];
2545 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2546 [[NSNotificationCenter defaultCenter] addObserver:self
2547 selector:@selector(serverConnectionDidDie:)
2548 name:NSConnectionDidDieNotification object:svrConn];
2555 - (NSConnection *)connectionForServerPort:(int)port
2558 NSEnumerator *e = [connectionNameDict objectEnumerator];
2560 while ((conn = [e nextObject])) {
2561 // HACK! Assume connection uses mach ports.
2562 if (port == [(NSMachPort*)[conn sendPort] machPort])
2569 - (void)serverConnectionDidDie:(NSNotification *)notification
2571 //NSLog(@"%s%@", _cmd, notification);
2573 NSConnection *svrConn = [notification object];
2575 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2576 [[NSNotificationCenter defaultCenter]
2578 name:NSConnectionDidDieNotification
2581 [connectionNameDict removeObjectsForKeys:
2582 [connectionNameDict allKeysForObject:svrConn]];
2584 // HACK! Assume connection uses mach ports.
2585 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2586 NSNumber *key = [NSNumber numberWithInt:port];
2588 [clientProxyDict removeObjectForKey:key];
2589 [serverReplyDict removeObjectForKey:key];
2592 - (void)addClient:(NSDistantObject *)client
2594 NSConnection *conn = [client connectionForProxy];
2595 // HACK! Assume connection uses mach ports.
2596 int port = [(NSMachPort*)[conn sendPort] machPort];
2597 NSNumber *key = [NSNumber numberWithInt:port];
2599 if (![clientProxyDict objectForKey:key]) {
2600 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2601 [clientProxyDict setObject:client forKey:key];
2604 // NOTE: 'clientWindow' is a global variable which is used by <client>
2605 clientWindow = port;
2608 - (NSString *)alternateServerNameForName:(NSString *)name
2610 if (!(name && [name length] > 0))
2613 // Only look for alternates if 'name' doesn't end in a digit.
2614 unichar lastChar = [name characterAtIndex:[name length]-1];
2615 if (lastChar >= '0' && lastChar <= '9')
2618 // Look for alternates among all current servers.
2619 NSArray *list = [self serverList];
2620 if (!(list && [list count] > 0))
2623 // Filter out servers starting with 'name' and ending with a number. The
2624 // (?i) pattern ensures that the match is case insensitive.
2625 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2626 NSPredicate *pred = [NSPredicate predicateWithFormat:
2627 @"SELF MATCHES %@", pat];
2628 list = [list filteredArrayUsingPredicate:pred];
2629 if ([list count] > 0) {
2630 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2631 return [list objectAtIndex:0];
2637 @end // MMBackend (ClientServer)
2642 @implementation NSString (MMServerNameCompare)
2643 - (NSComparisonResult)serverNameCompare:(NSString *)string
2645 return [self compare:string
2646 options:NSCaseInsensitiveSearch|NSNumericSearch];
2653 static int eventModifierFlagsToVimModMask(int modifierFlags)
2657 if (modifierFlags & NSShiftKeyMask)
2658 modMask |= MOD_MASK_SHIFT;
2659 if (modifierFlags & NSControlKeyMask)
2660 modMask |= MOD_MASK_CTRL;
2661 if (modifierFlags & NSAlternateKeyMask)
2662 modMask |= MOD_MASK_ALT;
2663 if (modifierFlags & NSCommandKeyMask)
2664 modMask |= MOD_MASK_CMD;
2669 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2673 if (modifierFlags & NSShiftKeyMask)
2674 modMask |= MOUSE_SHIFT;
2675 if (modifierFlags & NSControlKeyMask)
2676 modMask |= MOUSE_CTRL;
2677 if (modifierFlags & NSAlternateKeyMask)
2678 modMask |= MOUSE_ALT;
2683 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2685 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2687 return (buttonNumber >= 0 && buttonNumber < 3)
2688 ? mouseButton[buttonNumber] : -1;
2693 // This function is modeled after the VimToPython function found in if_python.c
2694 // NB This does a deep copy by value, it does not lookup references like the
2695 // VimToPython function does. This is because I didn't want to deal with the
2696 // retain cycles that this would create, and we can cover 99% of the use cases
2697 // by ignoring it. If we ever switch to using GC in MacVim then this
2698 // functionality can be implemented easily.
2699 static id vimToCocoa(typval_T * tv, int depth)
2705 // Avoid infinite recursion
2710 if (tv->v_type == VAR_STRING) {
2711 char_u * val = tv->vval.v_string;
2712 // val can be NULL if the string is empty
2714 result = [NSString string];
2717 val = CONVERT_TO_UTF8(val);
2719 result = [NSString stringWithUTF8String:(char*)val];
2721 CONVERT_TO_UTF8_FREE(val);
2724 } else if (tv->v_type == VAR_NUMBER) {
2725 // looks like sizeof(varnumber_T) is always <= sizeof(long)
2726 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2727 } else if (tv->v_type == VAR_LIST) {
2728 list_T * list = tv->vval.v_list;
2731 NSMutableArray * arr = result = [NSMutableArray array];
2734 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2735 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2736 [arr addObject:newObj];
2739 } else if (tv->v_type == VAR_DICT) {
2740 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2742 if (tv->vval.v_dict != NULL) {
2743 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2744 int todo = ht->ht_used;
2748 for (hi = ht->ht_array; todo > 0; ++hi) {
2749 if (!HASHITEM_EMPTY(hi)) {
2752 di = dict_lookup(hi);
2753 newObj = vimToCocoa(&di->di_tv, depth + 1);
2755 char_u * keyval = hi->hi_key;
2757 keyval = CONVERT_TO_UTF8(keyval);
2759 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2761 CONVERT_TO_UTF8_FREE(keyval);
2763 [dict setObject:newObj forKey:key];
2767 } else { // only func refs should fall into this category?
2775 // This function is modeled after eval_client_expr_to_string found in main.c
2776 // Returns nil if there was an error evaluating the expression, and writes a
2777 // message to errorStr.
2778 // TODO Get the error that occurred while evaluating the expression in vim
2780 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2783 char_u *s = (char_u*)[expr UTF8String];
2786 s = CONVERT_FROM_UTF8(s);
2789 int save_dbl = debug_break_level;
2790 int save_ro = redir_off;
2792 debug_break_level = -1;
2796 typval_T * tvres = eval_expr(s, NULL);
2798 debug_break_level = save_dbl;
2799 redir_off = save_ro;
2806 CONVERT_FROM_UTF8_FREE(s);
2811 gui_update_cursor(FALSE, FALSE);
2814 if (tvres == NULL) {
2816 *errstr = @"Expression evaluation failed.";
2819 id res = vimToCocoa(tvres, 1);
2824 *errstr = @"Conversion to cocoa values failed.";
2832 @implementation NSString (VimStrings)
2834 + (id)stringWithVimString:(char_u *)s
2836 // This method ensures a non-nil string is returned. If 's' cannot be
2837 // converted to a utf-8 string it is assumed to be latin-1. If conversion
2838 // still fails an empty NSString is returned.
2839 NSString *string = nil;
2842 s = CONVERT_TO_UTF8(s);
2844 string = [NSString stringWithUTF8String:(char*)s];
2846 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2848 string = [NSString stringWithCString:(char*)s
2849 encoding:NSISOLatin1StringEncoding];
2852 CONVERT_TO_UTF8_FREE(s);
2856 return string != nil ? string : [NSString string];
2859 - (char_u *)vimStringSave
2861 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2864 s = CONVERT_FROM_UTF8(s);
2866 ret = vim_strsave(s);
2868 CONVERT_FROM_UTF8_FREE(s);
2874 @end // NSString (VimStrings)