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:(NSString *)text;
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;
112 - (void)handleFindReplace:(NSDictionary *)args;
117 @interface MMBackend (ClientServer)
118 - (NSString *)connectionNameFromServerName:(NSString *)name;
119 - (NSConnection *)connectionForServerName:(NSString *)name;
120 - (NSConnection *)connectionForServerPort:(int)port;
121 - (void)serverConnectionDidDie:(NSNotification *)notification;
122 - (void)addClient:(NSDistantObject *)client;
123 - (NSString *)alternateServerNameForName:(NSString *)name;
128 @implementation MMBackend
130 + (MMBackend *)sharedInstance
132 static MMBackend *singleton = nil;
133 return singleton ? singleton : (singleton = [MMBackend new]);
139 if (!self) return nil;
141 fontContainerRef = loadFonts();
143 outputQueue = [[NSMutableArray alloc] init];
144 inputQueue = [[NSMutableArray alloc] init];
145 drawData = [[NSMutableData alloc] initWithCapacity:1024];
146 connectionNameDict = [[NSMutableDictionary alloc] init];
147 clientProxyDict = [[NSMutableDictionary alloc] init];
148 serverReplyDict = [[NSMutableDictionary alloc] init];
150 NSBundle *mainBundle = [NSBundle mainBundle];
151 NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
153 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
155 path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
157 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
160 path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
162 actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
164 if (!(colorDict && sysColorDict && actionDict))
165 NSLog(@"ERROR: Failed to load dictionaries.%@",
166 MMSymlinkWarningString);
173 //NSLog(@"%@ %s", [self className], _cmd);
174 [[NSNotificationCenter defaultCenter] removeObserver:self];
176 [oldWideFont release]; oldWideFont = nil;
177 [blinkTimer release]; blinkTimer = nil;
178 [alternateServerName release]; alternateServerName = nil;
179 [serverReplyDict release]; serverReplyDict = nil;
180 [clientProxyDict release]; clientProxyDict = nil;
181 [connectionNameDict release]; connectionNameDict = nil;
182 [inputQueue release]; inputQueue = nil;
183 [outputQueue release]; outputQueue = nil;
184 [drawData release]; drawData = nil;
185 [frontendProxy release]; frontendProxy = nil;
186 [connection release]; connection = nil;
187 [actionDict release]; actionDict = nil;
188 [sysColorDict release]; sysColorDict = nil;
189 [colorDict release]; colorDict = nil;
194 - (void)setBackgroundColor:(int)color
196 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
199 - (void)setForegroundColor:(int)color
201 foregroundColor = MM_COLOR(color);
204 - (void)setSpecialColor:(int)color
206 specialColor = MM_COLOR(color);
209 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
211 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
212 defaultForegroundColor = MM_COLOR(fg);
214 NSMutableData *data = [NSMutableData data];
216 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
217 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
219 [self queueMessage:SetDefaultColorsMsgID data:data];
222 - (NSConnection *)connection
225 // NOTE! If the name of the connection changes here it must also be
226 // updated in MMAppController.m.
227 NSString *name = [NSString stringWithFormat:@"%@-connection",
228 [[NSBundle mainBundle] bundlePath]];
230 connection = [NSConnection connectionWithRegisteredName:name host:nil];
234 // NOTE: 'connection' may be nil here.
238 - (NSDictionary *)actionDict
243 - (int)initialWindowLayout
245 return initialWindowLayout;
248 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
250 [self queueMessage:msgid data:[props dictionaryAsData]];
255 if (![self connection]) {
257 // This is a preloaded process and as such should not cause the
258 // MacVim to be opened. We probably got here as a result of the
259 // user quitting MacVim while the process was preloading, so exit
261 // (Don't use mch_exit() since it assumes the process has properly
266 NSBundle *mainBundle = [NSBundle mainBundle];
271 // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
272 // the API to pass Apple Event parameters is broken on 10.4).
273 NSString *path = [mainBundle bundlePath];
274 status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
275 if (noErr == status) {
276 // Pass parameter to the 'Open' Apple Event that tells MacVim not
277 // to open an untitled window.
278 NSAppleEventDescriptor *desc =
279 [NSAppleEventDescriptor recordDescriptor];
280 [desc setParamDescriptor:
281 [NSAppleEventDescriptor descriptorWithBoolean:NO]
282 forKeyword:keyMMUntitledWindow];
284 LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
285 kLSLaunchDefaults, NULL };
286 status = LSOpenFromRefSpec(&spec, NULL);
289 if (noErr != status) {
290 NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
291 path, MMSymlinkWarningString);
295 // Launch MacVim using NSTask. For some reason the above code using
296 // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
297 // fails, the dock icon starts bouncing and never stops). It seems
298 // like rebuilding the Launch Services database takes care of this
299 // problem, but the NSTask way seems more stable so stick with it.
301 // NOTE! Using NSTask to launch the GUI has the negative side-effect
302 // that the GUI won't be activated (or raised) so there is a hack in
303 // MMAppController which raises the app when a new window is opened.
304 NSMutableArray *args = [NSMutableArray arrayWithObjects:
305 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
306 NSString *exeName = [[mainBundle infoDictionary]
307 objectForKey:@"CFBundleExecutable"];
308 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
310 NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
311 MMSymlinkWarningString);
315 [NSTask launchedTaskWithLaunchPath:path arguments:args];
318 // HACK! Poll the mach bootstrap server until it returns a valid
319 // connection to detect that MacVim has finished launching. Also set a
320 // time-out date so that we don't get stuck doing this forever.
321 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
322 while (![self connection] &&
323 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
324 [[NSRunLoop currentRunLoop]
325 runMode:NSDefaultRunLoopMode
326 beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
328 // NOTE: [self connection] will set 'connection' as a side-effect.
330 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
337 [[NSNotificationCenter defaultCenter] addObserver:self
338 selector:@selector(connectionDidDie:)
339 name:NSConnectionDidDieNotification object:connection];
341 id proxy = [connection rootProxy];
342 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
344 int pid = [[NSProcessInfo processInfo] processIdentifier];
346 frontendProxy = [proxy connectBackend:self pid:pid];
348 [frontendProxy retain];
349 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
353 @catch (NSException *e) {
354 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
360 - (BOOL)openGUIWindow
362 [self queueMessage:OpenWindowMsgID data:nil];
368 int type = ClearAllDrawType;
370 // Any draw commands in queue are effectively obsolete since this clearAll
371 // will negate any effect they have, therefore we may as well clear the
373 [drawData setLength:0];
375 [drawData appendBytes:&type length:sizeof(int)];
378 - (void)clearBlockFromRow:(int)row1 column:(int)col1
379 toRow:(int)row2 column:(int)col2
381 int type = ClearBlockDrawType;
383 [drawData appendBytes:&type length:sizeof(int)];
385 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
386 [drawData appendBytes:&row1 length:sizeof(int)];
387 [drawData appendBytes:&col1 length:sizeof(int)];
388 [drawData appendBytes:&row2 length:sizeof(int)];
389 [drawData appendBytes:&col2 length:sizeof(int)];
392 - (void)deleteLinesFromRow:(int)row count:(int)count
393 scrollBottom:(int)bottom left:(int)left right:(int)right
395 int type = DeleteLinesDrawType;
397 [drawData appendBytes:&type length:sizeof(int)];
399 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
400 [drawData appendBytes:&row length:sizeof(int)];
401 [drawData appendBytes:&count length:sizeof(int)];
402 [drawData appendBytes:&bottom length:sizeof(int)];
403 [drawData appendBytes:&left length:sizeof(int)];
404 [drawData appendBytes:&right length:sizeof(int)];
407 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
408 cells:(int)cells flags:(int)flags
410 if (len <= 0 || cells <= 0) return;
412 int type = DrawStringDrawType;
414 [drawData appendBytes:&type length:sizeof(int)];
416 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
417 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
418 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
419 [drawData appendBytes:&row length:sizeof(int)];
420 [drawData appendBytes:&col length:sizeof(int)];
421 [drawData appendBytes:&cells length:sizeof(int)];
422 [drawData appendBytes:&flags length:sizeof(int)];
423 [drawData appendBytes:&len length:sizeof(int)];
424 [drawData appendBytes:s length:len];
427 - (void)insertLinesFromRow:(int)row count:(int)count
428 scrollBottom:(int)bottom left:(int)left right:(int)right
430 int type = InsertLinesDrawType;
432 [drawData appendBytes:&type length:sizeof(int)];
434 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
435 [drawData appendBytes:&row length:sizeof(int)];
436 [drawData appendBytes:&count length:sizeof(int)];
437 [drawData appendBytes:&bottom length:sizeof(int)];
438 [drawData appendBytes:&left length:sizeof(int)];
439 [drawData appendBytes:&right length:sizeof(int)];
442 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
443 fraction:(int)percent color:(int)color
445 int type = DrawCursorDrawType;
446 unsigned uc = MM_COLOR(color);
448 [drawData appendBytes:&type length:sizeof(int)];
450 [drawData appendBytes:&uc length:sizeof(unsigned)];
451 [drawData appendBytes:&row length:sizeof(int)];
452 [drawData appendBytes:&col length:sizeof(int)];
453 [drawData appendBytes:&shape length:sizeof(int)];
454 [drawData appendBytes:&percent length:sizeof(int)];
457 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
458 numColumns:(int)nc invert:(int)invert
460 int type = DrawInvertedRectDrawType;
461 [drawData appendBytes:&type length:sizeof(int)];
463 [drawData appendBytes:&row length:sizeof(int)];
464 [drawData appendBytes:&col length:sizeof(int)];
465 [drawData appendBytes:&nr length:sizeof(int)];
466 [drawData appendBytes:&nc length:sizeof(int)];
467 [drawData appendBytes:&invert length:sizeof(int)];
472 // Keep running the run-loop until there is no more input to process.
473 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
474 == kCFRunLoopRunHandledSource)
478 - (void)flushQueue:(BOOL)force
480 // NOTE: This variable allows for better control over when the queue is
481 // flushed. It can be set to YES at the beginning of a sequence of calls
482 // that may potentially add items to the queue, and then restored back to
484 if (flushDisabled) return;
486 if ([drawData length] > 0) {
487 // HACK! Detect changes to 'guifontwide'.
488 if (gui.wide_font != (GuiFont)oldWideFont) {
489 [oldWideFont release];
490 oldWideFont = [(NSFont*)gui.wide_font retain];
491 [self setWideFont:oldWideFont];
494 int type = SetCursorPosDrawType;
495 [drawData appendBytes:&type length:sizeof(type)];
496 [drawData appendBytes:&gui.row length:sizeof(gui.row)];
497 [drawData appendBytes:&gui.col length:sizeof(gui.col)];
499 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
500 [drawData setLength:0];
503 if ([outputQueue count] > 0) {
504 [self insertVimStateMessage];
507 [frontendProxy processCommandQueue:outputQueue];
509 @catch (NSException *e) {
510 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
513 [outputQueue removeAllObjects];
517 - (BOOL)waitForInput:(int)milliseconds
519 // Return NO if we timed out waiting for input, otherwise return YES.
520 BOOL inputReceived = NO;
522 // Only start the run loop if the input queue is empty, otherwise process
523 // the input first so that the input on queue isn't delayed.
524 if ([inputQueue count]) {
527 // Wait for the specified amount of time, unless 'milliseconds' is
528 // negative in which case we wait "forever" (1e6 seconds translates to
529 // approximately 11 days).
530 CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
532 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
533 == kCFRunLoopRunHandledSource) {
534 // In order to ensure that all input on the run-loop has been
535 // processed we set the timeout to 0 and keep processing until the
536 // run-loop times out.
542 // The above calls may have placed messages on the input queue so process
543 // it now. This call may enter a blocking loop.
544 if ([inputQueue count] > 0)
545 [self processInputQueue];
547 return inputReceived;
552 // NOTE: This is called if mch_exit() is called. Since we assume here that
553 // the process has started properly, be sure to use exit() instead of
554 // mch_exit() to prematurely terminate a process.
556 // To notify MacVim that this Vim process is exiting we could simply
557 // invalidate the connection and it would automatically receive a
558 // connectionDidDie: notification. However, this notification seems to
559 // take up to 300 ms to arrive which is quite a noticeable delay. Instead
560 // we immediately send a message to MacVim asking it to close the window
561 // belonging to this process, and then we invalidate the connection (in
562 // case the message got lost).
564 // Make sure no connectionDidDie: notification is received now that we are
566 [[NSNotificationCenter defaultCenter] removeObserver:self];
568 if ([connection isValid]) {
570 // Flush the entire queue in case a VimLeave autocommand added
571 // something to the queue.
572 [self queueMessage:CloseWindowMsgID data:nil];
573 [frontendProxy processCommandQueue:outputQueue];
575 @catch (NSException *e) {
576 NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
579 [connection invalidate];
582 #ifdef MAC_CLIENTSERVER
583 // The default connection is used for the client/server code.
584 [[NSConnection defaultConnection] setRootObject:nil];
585 [[NSConnection defaultConnection] invalidate];
588 if (fontContainerRef) {
589 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
590 fontContainerRef = 0;
593 usleep(MMExitProcessDelay);
596 - (void)selectTab:(int)index
598 //NSLog(@"%s%d", _cmd, index);
601 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
602 [self queueMessage:SelectTabMsgID data:data];
607 //NSLog(@"%s", _cmd);
609 NSMutableData *data = [NSMutableData data];
611 int idx = tabpage_index(curtab) - 1;
612 [data appendBytes:&idx length:sizeof(int)];
615 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
616 // This function puts the label of the tab in the global 'NameBuff'.
617 get_tabline_label(tp, FALSE);
618 char_u *s = NameBuff;
620 if (len <= 0) continue;
623 s = CONVERT_TO_UTF8(s);
626 // Count the number of windows in the tabpage.
627 //win_T *wp = tp->tp_firstwin;
629 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
631 //[data appendBytes:&wincount length:sizeof(int)];
632 [data appendBytes:&len length:sizeof(int)];
633 [data appendBytes:s length:len];
636 CONVERT_TO_UTF8_FREE(s);
640 [self queueMessage:UpdateTabBarMsgID data:data];
643 - (BOOL)tabBarVisible
645 return tabBarVisible;
648 - (void)showTabBar:(BOOL)enable
650 tabBarVisible = enable;
652 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
653 [self queueMessage:msgid data:nil];
656 - (void)setRows:(int)rows columns:(int)cols
658 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
660 int dim[] = { rows, cols };
661 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
663 [self queueMessage:SetTextDimensionsMsgID data:data];
666 - (void)setWindowTitle:(char *)title
668 NSMutableData *data = [NSMutableData data];
669 int len = strlen(title);
670 if (len <= 0) return;
672 [data appendBytes:&len length:sizeof(int)];
673 [data appendBytes:title length:len];
675 [self queueMessage:SetWindowTitleMsgID data:data];
678 - (void)setDocumentFilename:(char *)filename
680 NSMutableData *data = [NSMutableData data];
681 int len = filename ? strlen(filename) : 0;
683 [data appendBytes:&len length:sizeof(int)];
685 [data appendBytes:filename length:len];
687 [self queueMessage:SetDocumentFilenameMsgID data:data];
690 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
695 [frontendProxy showSavePanelWithAttributes:attr];
697 [self waitForDialogReturn];
699 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
700 s = [dialogReturn vimStringSave];
702 [dialogReturn release]; dialogReturn = nil;
704 @catch (NSException *e) {
705 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
711 - (oneway void)setDialogReturn:(in bycopy id)obj
713 // NOTE: This is called by
714 // - [MMVimController panelDidEnd:::], and
715 // - [MMVimController alertDidEnd:::],
716 // to indicate that a save/open panel or alert has finished.
718 // We want to distinguish between "no dialog return yet" and "dialog
719 // returned nothing". The former can be tested with dialogReturn == nil,
720 // the latter with dialogReturn == [NSNull null].
721 if (!obj) obj = [NSNull null];
723 if (obj != dialogReturn) {
724 [dialogReturn release];
725 dialogReturn = [obj retain];
729 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
734 [frontendProxy presentDialogWithAttributes:attr];
736 [self waitForDialogReturn];
738 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
739 && [dialogReturn count]) {
740 retval = [[dialogReturn objectAtIndex:0] intValue];
741 if (txtfield && [dialogReturn count] > 1) {
742 NSString *retString = [dialogReturn objectAtIndex:1];
743 char_u *ret = (char_u*)[retString UTF8String];
745 ret = CONVERT_FROM_UTF8(ret);
747 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
749 CONVERT_FROM_UTF8_FREE(ret);
754 [dialogReturn release]; dialogReturn = nil;
756 @catch (NSException *e) {
757 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
763 - (void)showToolbar:(int)enable flags:(int)flags
765 NSMutableData *data = [NSMutableData data];
767 [data appendBytes:&enable length:sizeof(int)];
768 [data appendBytes:&flags length:sizeof(int)];
770 [self queueMessage:ShowToolbarMsgID data:data];
773 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
775 NSMutableData *data = [NSMutableData data];
777 [data appendBytes:&ident length:sizeof(long)];
778 [data appendBytes:&type length:sizeof(int)];
780 [self queueMessage:CreateScrollbarMsgID data:data];
783 - (void)destroyScrollbarWithIdentifier:(long)ident
785 NSMutableData *data = [NSMutableData data];
786 [data appendBytes:&ident length:sizeof(long)];
788 [self queueMessage:DestroyScrollbarMsgID data:data];
791 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
793 NSMutableData *data = [NSMutableData data];
795 [data appendBytes:&ident length:sizeof(long)];
796 [data appendBytes:&visible length:sizeof(int)];
798 [self queueMessage:ShowScrollbarMsgID data:data];
801 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
803 NSMutableData *data = [NSMutableData data];
805 [data appendBytes:&ident length:sizeof(long)];
806 [data appendBytes:&pos length:sizeof(int)];
807 [data appendBytes:&len length:sizeof(int)];
809 [self queueMessage:SetScrollbarPositionMsgID data:data];
812 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
813 identifier:(long)ident
815 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
816 float prop = (float)size/(max+1);
817 if (fval < 0) fval = 0;
818 else if (fval > 1.0f) fval = 1.0f;
819 if (prop < 0) prop = 0;
820 else if (prop > 1.0f) prop = 1.0f;
822 NSMutableData *data = [NSMutableData data];
824 [data appendBytes:&ident length:sizeof(long)];
825 [data appendBytes:&fval length:sizeof(float)];
826 [data appendBytes:&prop length:sizeof(float)];
828 [self queueMessage:SetScrollbarThumbMsgID data:data];
831 - (void)setFont:(NSFont *)font
833 NSString *fontName = [font displayName];
834 float size = [font pointSize];
835 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
837 NSMutableData *data = [NSMutableData data];
839 [data appendBytes:&size length:sizeof(float)];
840 [data appendBytes:&len length:sizeof(int)];
841 [data appendBytes:[fontName UTF8String] length:len];
843 [self queueMessage:SetFontMsgID data:data];
847 - (void)setWideFont:(NSFont *)font
849 NSString *fontName = [font displayName];
850 float size = [font pointSize];
851 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
852 NSMutableData *data = [NSMutableData data];
854 [data appendBytes:&size length:sizeof(float)];
855 [data appendBytes:&len length:sizeof(int)];
857 [data appendBytes:[fontName UTF8String] length:len];
859 [self queueMessage:SetWideFontMsgID data:data];
862 - (void)executeActionWithName:(NSString *)name
864 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
867 NSMutableData *data = [NSMutableData data];
869 [data appendBytes:&len length:sizeof(int)];
870 [data appendBytes:[name UTF8String] length:len];
872 [self queueMessage:ExecuteActionMsgID data:data];
876 - (void)setMouseShape:(int)shape
878 NSMutableData *data = [NSMutableData data];
879 [data appendBytes:&shape length:sizeof(int)];
880 [self queueMessage:SetMouseShapeMsgID data:data];
883 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
885 // Vim specifies times in milliseconds, whereas Cocoa wants them in
887 blinkWaitInterval = .001f*wait;
888 blinkOnInterval = .001f*on;
889 blinkOffInterval = .001f*off;
895 [blinkTimer invalidate];
896 [blinkTimer release];
900 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
902 blinkState = MMBlinkStateOn;
904 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
906 selector:@selector(blinkTimerFired:)
907 userInfo:nil repeats:NO] retain];
908 gui_update_cursor(TRUE, FALSE);
909 [self flushQueue:YES];
915 if (MMBlinkStateOff == blinkState) {
916 gui_update_cursor(TRUE, FALSE);
917 [self flushQueue:YES];
920 blinkState = MMBlinkStateNone;
923 - (void)adjustLinespace:(int)linespace
925 NSMutableData *data = [NSMutableData data];
926 [data appendBytes:&linespace length:sizeof(int)];
927 [self queueMessage:AdjustLinespaceMsgID data:data];
932 [self queueMessage:ActivateMsgID data:nil];
935 - (void)setPreEditRow:(int)row column:(int)col
937 NSMutableData *data = [NSMutableData data];
938 [data appendBytes:&row length:sizeof(int)];
939 [data appendBytes:&col length:sizeof(int)];
940 [self queueMessage:SetPreEditPositionMsgID data:data];
943 - (int)lookupColorWithKey:(NSString *)key
945 if (!(key && [key length] > 0))
948 NSString *stripKey = [[[[key lowercaseString]
949 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
950 componentsSeparatedByString:@" "]
951 componentsJoinedByString:@""];
953 if (stripKey && [stripKey length] > 0) {
954 // First of all try to lookup key in the color dictionary; note that
955 // all keys in this dictionary are lowercase with no whitespace.
956 id obj = [colorDict objectForKey:stripKey];
957 if (obj) return [obj intValue];
959 // The key was not in the dictionary; is it perhaps of the form
961 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
962 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
963 [scanner setScanLocation:1];
965 if ([scanner scanHexInt:&hex]) {
970 // As a last resort, check if it is one of the system defined colors.
971 // The keys in this dictionary are also lowercase with no whitespace.
972 obj = [sysColorDict objectForKey:stripKey];
974 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
977 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
978 [col getRed:&r green:&g blue:&b alpha:&a];
979 return (((int)(r*255+.5f) & 0xff) << 16)
980 + (((int)(g*255+.5f) & 0xff) << 8)
981 + ((int)(b*255+.5f) & 0xff);
986 //NSLog(@"WARNING: No color with key %@ found.", stripKey);
990 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
992 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
995 while ((obj = [e nextObject])) {
996 if ([value isEqual:obj])
1003 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1005 NSMutableData *data = [NSMutableData data];
1006 [data appendBytes:&fuoptions length:sizeof(int)];
1008 [data appendBytes:&bg length:sizeof(int)];
1009 [self queueMessage:EnterFullscreenMsgID data:data];
1012 - (void)leaveFullscreen
1014 [self queueMessage:LeaveFullscreenMsgID data:nil];
1017 - (void)setFullscreenBackgroundColor:(int)color
1019 NSMutableData *data = [NSMutableData data];
1020 color = MM_COLOR(color);
1021 [data appendBytes:&color length:sizeof(int)];
1023 [self queueMessage:SetFullscreenColorMsgID data:data];
1026 - (void)setAntialias:(BOOL)antialias
1028 int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1030 [self queueMessage:msgid data:nil];
1033 - (void)updateModifiedFlag
1035 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1037 int msgid = [self checkForModifiedBuffers]
1038 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1040 [self queueMessage:msgid data:nil];
1043 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1045 // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1046 // queue is processed since that only happens in waitForInput: (and Vim
1047 // regularly checks for Ctrl-C in between waiting for input).
1049 BOOL interrupt = NO;
1050 if (msgid == InterruptMsgID) {
1052 } else if (InsertTextMsgID == msgid && data != nil) {
1053 const void *bytes = [data bytes];
1054 bytes += sizeof(int);
1055 int len = *((int*)bytes); bytes += sizeof(int);
1057 char_u *str = (char_u*)bytes;
1058 if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1059 (str[0] == intr_char && intr_char != Ctrl_C))
1066 [inputQueue removeAllObjects];
1070 // Remove all previous instances of this message from the input queue, else
1071 // the input queue may fill up as a result of Vim not being able to keep up
1072 // with the speed at which new messages are received.
1073 // Keyboard input is never dropped, unless the input represents and
1074 // auto-repeated key.
1076 BOOL isKeyRepeat = NO;
1077 BOOL isKeyboardInput = NO;
1079 if (data && (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1080 CmdKeyMsgID == msgid)) {
1081 isKeyboardInput = YES;
1083 // The lowest bit of the first int is set if this key is a repeat.
1084 int flags = *((int*)[data bytes]);
1089 // Keyboard input is not removed from the queue; repeats are ignored if
1090 // there already is keyboard input on the input queue.
1091 if (isKeyRepeat || !isKeyboardInput) {
1092 int i, count = [inputQueue count];
1093 for (i = 1; i < count; i+=2) {
1094 if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1098 [inputQueue removeObjectAtIndex:i];
1099 [inputQueue removeObjectAtIndex:i-1];
1105 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1106 [inputQueue addObject:(data ? (id)data : [NSNull null])];
1109 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1111 // This is just a convenience method that allows the frontend to delay
1112 // sending messages.
1113 int i, count = [messages count];
1114 for (i = 1; i < count; i+=2)
1115 [self processInput:[[messages objectAtIndex:i-1] intValue]
1116 data:[messages objectAtIndex:i]];
1119 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1120 errorString:(out bycopy NSString **)errstr
1122 return evalExprCocoa(expr, errstr);
1126 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1128 NSString *eval = nil;
1129 char_u *s = (char_u*)[expr UTF8String];
1132 s = CONVERT_FROM_UTF8(s);
1135 char_u *res = eval_client_expr_to_string(s);
1138 CONVERT_FROM_UTF8_FREE(s);
1144 s = CONVERT_TO_UTF8(s);
1146 eval = [NSString stringWithUTF8String:(char*)s];
1148 CONVERT_TO_UTF8_FREE(s);
1156 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1158 // TODO: This method should share code with clip_mch_request_selection().
1160 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1161 // If there is no pasteboard, return YES to indicate that there is text
1166 clip_copy_selection();
1168 // Get the text to put on the pasteboard.
1169 long_u llen = 0; char_u *str = 0;
1170 int type = clip_convert_selection(&str, &llen, &clip_star);
1174 // TODO: Avoid overflow.
1175 int len = (int)llen;
1177 if (output_conv.vc_type != CONV_NONE) {
1178 char_u *conv_str = string_convert(&output_conv, str, &len);
1186 NSString *string = [[NSString alloc]
1187 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1189 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1190 [pboard declareTypes:types owner:nil];
1191 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1202 - (oneway void)addReply:(in bycopy NSString *)reply
1203 server:(in byref id <MMVimServerProtocol>)server
1205 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1207 // Replies might come at any time and in any order so we keep them in an
1208 // array inside a dictionary with the send port used as key.
1210 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1211 // HACK! Assume connection uses mach ports.
1212 int port = [(NSMachPort*)[conn sendPort] machPort];
1213 NSNumber *key = [NSNumber numberWithInt:port];
1215 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1217 replies = [NSMutableArray array];
1218 [serverReplyDict setObject:replies forKey:key];
1221 [replies addObject:reply];
1224 - (void)addInput:(in bycopy NSString *)input
1225 client:(in byref id <MMVimClientProtocol>)client
1227 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1229 [self addInput:input];
1230 [self addClient:(id)client];
1233 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1234 client:(in byref id <MMVimClientProtocol>)client
1236 [self addClient:(id)client];
1237 return [self evaluateExpression:expr];
1240 - (void)registerServerWithName:(NSString *)name
1242 NSString *svrName = name;
1243 NSConnection *svrConn = [NSConnection defaultConnection];
1246 for (i = 0; i < MMServerMax; ++i) {
1247 NSString *connName = [self connectionNameFromServerName:svrName];
1249 if ([svrConn registerName:connName]) {
1250 //NSLog(@"Registered server with name: %@", svrName);
1252 // TODO: Set request/reply time-outs to something else?
1254 // Don't wait for requests (time-out means that the message is
1256 [svrConn setRequestTimeout:0];
1257 //[svrConn setReplyTimeout:MMReplyTimeout];
1258 [svrConn setRootObject:self];
1260 // NOTE: 'serverName' is a global variable
1261 serverName = [svrName vimStringSave];
1263 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1266 need_maketitle = TRUE;
1268 [self queueMessage:SetServerNameMsgID data:
1269 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1273 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1277 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1278 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1281 // NOTE: If 'name' equals 'serverName' then the request is local (client
1282 // and server are the same). This case is not handled separately, so a
1283 // connection will be set up anyway (this simplifies the code).
1285 NSConnection *conn = [self connectionForServerName:name];
1288 char_u *s = (char_u*)[name UTF8String];
1290 s = CONVERT_FROM_UTF8(s);
1292 EMSG2(_(e_noserver), s);
1294 CONVERT_FROM_UTF8_FREE(s);
1301 // HACK! Assume connection uses mach ports.
1302 *port = [(NSMachPort*)[conn sendPort] machPort];
1305 id proxy = [conn rootProxy];
1306 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1310 NSString *eval = [proxy evaluateExpression:string client:self];
1313 *reply = [eval vimStringSave];
1315 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1322 [proxy addInput:string client:self];
1325 @catch (NSException *e) {
1326 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1333 - (NSArray *)serverList
1335 NSArray *list = nil;
1337 if ([self connection]) {
1338 id proxy = [connection rootProxy];
1339 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1342 list = [proxy serverList];
1344 @catch (NSException *e) {
1345 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1348 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1354 - (NSString *)peekForReplyOnPort:(int)port
1356 //NSLog(@"%s%d", _cmd, port);
1358 NSNumber *key = [NSNumber numberWithInt:port];
1359 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1360 if (replies && [replies count]) {
1361 //NSLog(@" %d replies, topmost is: %@", [replies count],
1362 // [replies objectAtIndex:0]);
1363 return [replies objectAtIndex:0];
1366 //NSLog(@" No replies");
1370 - (NSString *)waitForReplyOnPort:(int)port
1372 //NSLog(@"%s%d", _cmd, port);
1374 NSConnection *conn = [self connectionForServerPort:port];
1378 NSNumber *key = [NSNumber numberWithInt:port];
1379 NSMutableArray *replies = nil;
1380 NSString *reply = nil;
1382 // Wait for reply as long as the connection to the server is valid (unless
1383 // user interrupts wait with Ctrl-C).
1384 while (!got_int && [conn isValid] &&
1385 !(replies = [serverReplyDict objectForKey:key])) {
1386 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1387 beforeDate:[NSDate distantFuture]];
1391 if ([replies count] > 0) {
1392 reply = [[replies objectAtIndex:0] retain];
1393 //NSLog(@" Got reply: %@", reply);
1394 [replies removeObjectAtIndex:0];
1395 [reply autorelease];
1398 if ([replies count] == 0)
1399 [serverReplyDict removeObjectForKey:key];
1405 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1407 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1410 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1411 [client addReply:reply server:self];
1414 @catch (NSException *e) {
1415 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1418 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1429 - (void)setWaitForAck:(BOOL)yn
1434 - (void)waitForConnectionAcknowledgement
1436 if (!waitForAck) return;
1438 while (waitForAck && !got_int && [connection isValid] && !isTerminating) {
1439 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1440 beforeDate:[NSDate distantFuture]];
1441 //NSLog(@" waitForAck=%d got_int=%d isTerminating=%d isValid=%d",
1442 // waitForAck, got_int, isTerminating, [connection isValid]);
1446 // Never received a connection acknowledgement, so die.
1447 [[NSNotificationCenter defaultCenter] removeObserver:self];
1448 [frontendProxy release]; frontendProxy = nil;
1450 // NOTE: We intentionally do not call mch_exit() since this in turn
1451 // will lead to -[MMBackend exit] getting called which we want to
1453 usleep(MMExitProcessDelay);
1457 [self processInputQueue];
1460 - (oneway void)acknowledgeConnection
1462 //NSLog(@"%s", _cmd);
1470 @implementation MMBackend (Private)
1472 - (void)waitForDialogReturn
1474 // Keep processing the run loop until a dialog returns. To avoid getting
1475 // stuck in an endless loop (could happen if the setDialogReturn: message
1476 // was lost) we also do some paranoia checks.
1478 // Note that in Cocoa the user can still resize windows and select menu
1479 // items while a sheet is being displayed, so we can't just wait for the
1480 // first message to arrive and assume that is the setDialogReturn: call.
1482 while (nil == dialogReturn && !got_int && [connection isValid]
1484 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1485 beforeDate:[NSDate distantFuture]];
1487 // Search for any resize messages on the input queue. All other messages
1488 // on the input queue are dropped. The reason why we single out resize
1489 // messages is because the user may have resized the window while a sheet
1491 int i, count = [inputQueue count];
1493 id textDimData = nil;
1495 for (i = count-2; i >= 0; i -= 2) {
1496 int msgid = [[inputQueue objectAtIndex:i] intValue];
1497 if (SetTextDimensionsMsgID == msgid) {
1498 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1504 [inputQueue removeAllObjects];
1507 [inputQueue addObject:
1508 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1509 [inputQueue addObject:textDimData];
1510 [textDimData release];
1515 - (void)insertVimStateMessage
1517 // NOTE: This is the place to add Vim state that needs to be accessed from
1518 // MacVim. Do not add state that could potentially require lots of memory
1519 // since this message gets sent each time the output queue is forcibly
1520 // flushed (e.g. storing the currently selected text would be a bad idea).
1521 // We take this approach of "pushing" the state to MacVim to avoid having
1522 // to make synchronous calls from MacVim to Vim in order to get state.
1524 BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1526 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1527 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1528 [NSNumber numberWithInt:p_mh], @"p_mh",
1529 [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1530 [NSNumber numberWithBool:mmta], @"p_mmta",
1533 // Put the state before all other messages.
1534 int msgid = SetVimStateMsgID;
1535 [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1536 [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1540 - (void)processInputQueue
1542 if ([inputQueue count] == 0) return;
1544 // NOTE: One of the input events may cause this method to be called
1545 // recursively, so copy the input queue to a local variable and clear the
1546 // queue before starting to process input events (otherwise we could get
1547 // stuck in an endless loop).
1548 NSArray *q = [inputQueue copy];
1549 unsigned i, count = [q count];
1551 [inputQueue removeAllObjects];
1553 for (i = 1; i < count; i+=2) {
1554 int msgid = [[q objectAtIndex:i-1] intValue];
1555 id data = [q objectAtIndex:i];
1556 if ([data isEqual:[NSNull null]])
1559 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1560 [self handleInputEvent:msgid data:data];
1564 //NSLog(@"Clear input event queue");
1567 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1569 if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1570 CmdKeyMsgID == msgid) {
1572 const void *bytes = [data bytes];
1573 int mods = *((int*)bytes); bytes += sizeof(int);
1574 int len = *((int*)bytes); bytes += sizeof(int);
1575 NSString *key = [[NSString alloc] initWithBytes:bytes
1577 encoding:NSUTF8StringEncoding];
1578 mods = eventModifierFlagsToVimModMask(mods);
1580 if (InsertTextMsgID == msgid)
1581 [self handleInsertText:key];
1583 [self handleKeyDown:key modifiers:mods];
1586 } else if (ScrollWheelMsgID == msgid) {
1588 const void *bytes = [data bytes];
1590 int row = *((int*)bytes); bytes += sizeof(int);
1591 int col = *((int*)bytes); bytes += sizeof(int);
1592 int flags = *((int*)bytes); bytes += sizeof(int);
1593 float dy = *((float*)bytes); bytes += sizeof(float);
1595 int button = MOUSE_5;
1596 if (dy > 0) button = MOUSE_4;
1598 flags = eventModifierFlagsToVimMouseModMask(flags);
1600 int numLines = (int)round(dy);
1601 if (numLines < 0) numLines = -numLines;
1602 if (numLines == 0) numLines = 1;
1604 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1605 gui.scroll_wheel_force = numLines;
1608 gui_send_mouse_event(button, col, row, NO, flags);
1609 } else if (MouseDownMsgID == msgid) {
1611 const void *bytes = [data bytes];
1613 int row = *((int*)bytes); bytes += sizeof(int);
1614 int col = *((int*)bytes); bytes += sizeof(int);
1615 int button = *((int*)bytes); bytes += sizeof(int);
1616 int flags = *((int*)bytes); bytes += sizeof(int);
1617 int count = *((int*)bytes); bytes += sizeof(int);
1619 button = eventButtonNumberToVimMouseButton(button);
1621 flags = eventModifierFlagsToVimMouseModMask(flags);
1622 gui_send_mouse_event(button, col, row, count>1, flags);
1624 } else if (MouseUpMsgID == msgid) {
1626 const void *bytes = [data bytes];
1628 int row = *((int*)bytes); bytes += sizeof(int);
1629 int col = *((int*)bytes); bytes += sizeof(int);
1630 int flags = *((int*)bytes); bytes += sizeof(int);
1632 flags = eventModifierFlagsToVimMouseModMask(flags);
1634 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1635 } else if (MouseDraggedMsgID == msgid) {
1637 const void *bytes = [data bytes];
1639 int row = *((int*)bytes); bytes += sizeof(int);
1640 int col = *((int*)bytes); bytes += sizeof(int);
1641 int flags = *((int*)bytes); bytes += sizeof(int);
1643 flags = eventModifierFlagsToVimMouseModMask(flags);
1645 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1646 } else if (MouseMovedMsgID == msgid) {
1647 const void *bytes = [data bytes];
1648 int row = *((int*)bytes); bytes += sizeof(int);
1649 int col = *((int*)bytes); bytes += sizeof(int);
1651 gui_mouse_moved(col, row);
1652 } else if (AddInputMsgID == msgid) {
1653 NSString *string = [[NSString alloc] initWithData:data
1654 encoding:NSUTF8StringEncoding];
1656 [self addInput:string];
1659 } else if (TerminateNowMsgID == msgid) {
1660 isTerminating = YES;
1661 } else if (SelectTabMsgID == msgid) {
1663 const void *bytes = [data bytes];
1664 int idx = *((int*)bytes) + 1;
1665 //NSLog(@"Selecting tab %d", idx);
1666 send_tabline_event(idx);
1667 } else if (CloseTabMsgID == msgid) {
1669 const void *bytes = [data bytes];
1670 int idx = *((int*)bytes) + 1;
1671 //NSLog(@"Closing tab %d", idx);
1672 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1673 } else if (AddNewTabMsgID == msgid) {
1674 //NSLog(@"Adding new tab");
1675 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1676 } else if (DraggedTabMsgID == msgid) {
1678 const void *bytes = [data bytes];
1679 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1681 int idx = *((int*)bytes);
1684 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1685 || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1687 const void *bytes = [data bytes];
1689 if (SetTextColumnsMsgID != msgid) {
1690 rows = *((int*)bytes); bytes += sizeof(int);
1693 if (SetTextRowsMsgID != msgid) {
1694 cols = *((int*)bytes); bytes += sizeof(int);
1698 if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1699 int dim[2] = { rows, cols };
1700 d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1701 msgid = SetTextDimensionsReplyMsgID;
1704 if (SetTextDimensionsMsgID == msgid)
1705 msgid = SetTextDimensionsReplyMsgID;
1707 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1708 // gui_resize_shell(), so we have to manually set the rows and columns
1709 // here since MacVim doesn't change the rows and columns to avoid
1710 // inconsistent states between Vim and MacVim. The message sent back
1711 // indicates that it is a reply to a message that originated in MacVim
1712 // since we need to be able to determine where a message originated.
1713 [self queueMessage:msgid data:d];
1715 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1716 gui_resize_shell(cols, rows);
1717 } else if (ExecuteMenuMsgID == msgid) {
1718 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1720 NSArray *desc = [attrs objectForKey:@"descriptor"];
1721 vimmenu_T *menu = menu_for_descriptor(desc);
1725 } else if (ToggleToolbarMsgID == msgid) {
1726 [self handleToggleToolbar];
1727 } else if (ScrollbarEventMsgID == msgid) {
1728 [self handleScrollbarEvent:data];
1729 } else if (SetFontMsgID == msgid) {
1730 [self handleSetFont:data];
1731 } else if (VimShouldCloseMsgID == msgid) {
1733 } else if (DropFilesMsgID == msgid) {
1734 [self handleDropFiles:data];
1735 } else if (DropStringMsgID == msgid) {
1736 [self handleDropString:data];
1737 } else if (GotFocusMsgID == msgid) {
1739 [self focusChange:YES];
1740 } else if (LostFocusMsgID == msgid) {
1742 [self focusChange:NO];
1743 } else if (SetMouseShapeMsgID == msgid) {
1744 const void *bytes = [data bytes];
1745 int shape = *((int*)bytes); bytes += sizeof(int);
1746 update_mouseshape(shape);
1747 } else if (XcodeModMsgID == msgid) {
1748 [self handleXcodeMod:data];
1749 } else if (OpenWithArgumentsMsgID == msgid) {
1750 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1751 } else if (FindReplaceMsgID == msgid) {
1752 [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1754 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1758 + (NSDictionary *)specialKeys
1760 static NSDictionary *specialKeys = nil;
1763 NSBundle *mainBundle = [NSBundle mainBundle];
1764 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1766 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1772 - (void)handleInsertText:(NSString *)text
1776 char_u *str = (char_u*)[text UTF8String];
1777 int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1780 char_u *conv_str = NULL;
1781 if (input_conv.vc_type != CONV_NONE) {
1782 conv_str = string_convert(&input_conv, str, &len);
1788 for (i = 0; i < len; ++i) {
1789 add_to_input_buf(str+i, 1);
1790 if (CSI == str[i]) {
1791 // NOTE: If the converted string contains the byte CSI, then it
1792 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1794 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1795 add_to_input_buf(extra, 2);
1805 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1807 // TODO: This code is a horrible mess -- clean up!
1810 char_u *chars = (char_u*)[key UTF8String];
1812 char_u *conv_str = NULL;
1814 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1816 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1817 // that new keys can easily be added.
1818 NSString *specialString = [[MMBackend specialKeys]
1820 if (specialString && [specialString length] > 1) {
1821 //NSLog(@"special key: %@", specialString);
1822 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1823 [specialString characterAtIndex:1]);
1825 ikey = simplify_key(ikey, &mods);
1830 special[1] = K_SECOND(ikey);
1831 special[2] = K_THIRD(ikey);
1835 } else if (1 == length && TAB == chars[0]) {
1836 // Tab is a trouble child:
1837 // - <Tab> is added to the input buffer as is
1838 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1839 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1840 // to be converted to utf-8
1841 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1842 // - <C-Tab> is reserved by Mac OS X
1843 // - <D-Tab> is reserved by Mac OS X
1848 if (mods & MOD_MASK_SHIFT) {
1849 mods &= ~MOD_MASK_SHIFT;
1851 special[1] = K_SECOND(K_S_TAB);
1852 special[2] = K_THIRD(K_S_TAB);
1854 } else if (mods & MOD_MASK_ALT) {
1855 int mtab = 0x80 | TAB;
1859 special[0] = (mtab >> 6) + 0xc0;
1860 special[1] = mtab & 0xbf;
1868 mods &= ~MOD_MASK_ALT;
1870 } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1871 // META key is treated separately. This code was taken from gui_w48.c
1872 // and gui_gtk_x11.c.
1874 int ch = simplify_key(chars[0], &mods);
1876 // Remove the SHIFT modifier for keys where it's already included,
1877 // e.g., '(' and '*'
1878 if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1879 mods &= ~MOD_MASK_SHIFT;
1881 // Interpret the ALT key as making the key META, include SHIFT, etc.
1882 ch = extract_modifiers(ch, &mods);
1888 string[len++] = CSI;
1889 string[len++] = KS_MODIFIER;
1890 string[len++] = mods;
1893 if (IS_SPECIAL(ch)) {
1894 string[len++] = CSI;
1895 string[len++] = K_SECOND(ch);
1896 string[len++] = K_THIRD(ch);
1900 // TODO: What if 'enc' is not "utf-8"?
1901 if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1902 string[len++] = ch & 0xbf;
1903 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1904 if (string[len-1] == CSI) {
1905 string[len++] = KS_EXTRA;
1906 string[len++] = (int)KE_CSI;
1912 add_to_input_buf(string, len);
1914 } else if (length > 0) {
1915 unichar c = [key characterAtIndex:0];
1916 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1917 // [key characterAtIndex:0], mods);
1919 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1920 // cleared since they are already added to the key by the AppKit.
1921 // Unfortunately, the only way to deal with when to clear the modifiers
1922 // or not seems to be to have hard-wired rules like this.
1923 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1924 || 0x9 == c || 0xd == c || ESC == c) ) {
1925 mods &= ~MOD_MASK_SHIFT;
1926 mods &= ~MOD_MASK_CTRL;
1927 //NSLog(@"clear shift ctrl");
1931 if (input_conv.vc_type != CONV_NONE) {
1932 conv_str = string_convert(&input_conv, chars, &length);
1939 if (chars && length > 0) {
1941 //NSLog(@"adding mods: %d", mods);
1943 modChars[1] = KS_MODIFIER;
1945 add_to_input_buf(modChars, 3);
1948 //NSLog(@"add to input buf: 0x%x", chars[0]);
1949 // TODO: Check for CSI bytes?
1950 add_to_input_buf(chars, length);
1959 - (void)queueMessage:(int)msgid data:(NSData *)data
1961 //if (msgid != EnableMenuItemMsgID)
1962 // NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1964 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1966 [outputQueue addObject:data];
1968 [outputQueue addObject:[NSData data]];
1971 - (void)connectionDidDie:(NSNotification *)notification
1973 // If the main connection to MacVim is lost this means that MacVim was
1974 // either quit (by the user chosing Quit on the MacVim menu), or it has
1975 // crashed. In the former case the flag 'isTerminating' is set and we then
1976 // quit cleanly; in the latter case we make sure the swap files are left
1979 // NOTE: This is not called if a Vim controller invalidates its connection.
1981 //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1985 getout_preserve_modified(1);
1988 - (void)blinkTimerFired:(NSTimer *)timer
1990 NSTimeInterval timeInterval = 0;
1992 [blinkTimer release];
1995 if (MMBlinkStateOn == blinkState) {
1996 gui_undraw_cursor();
1997 blinkState = MMBlinkStateOff;
1998 timeInterval = blinkOffInterval;
1999 } else if (MMBlinkStateOff == blinkState) {
2000 gui_update_cursor(TRUE, FALSE);
2001 blinkState = MMBlinkStateOn;
2002 timeInterval = blinkOnInterval;
2005 if (timeInterval > 0) {
2007 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2008 selector:@selector(blinkTimerFired:)
2009 userInfo:nil repeats:NO] retain];
2010 [self flushQueue:YES];
2014 - (void)focusChange:(BOOL)on
2016 gui_focus_change(on);
2019 - (void)handleToggleToolbar
2021 // If 'go' contains 'T', then remove it, else add it.
2023 char_u go[sizeof(GO_ALL)+2];
2028 p = vim_strchr(go, GO_TOOLBAR);
2032 char_u *end = go + len;
2038 go[len] = GO_TOOLBAR;
2042 set_option_value((char_u*)"guioptions", 0, go, 0);
2044 [self redrawScreen];
2047 - (void)handleScrollbarEvent:(NSData *)data
2051 const void *bytes = [data bytes];
2052 long ident = *((long*)bytes); bytes += sizeof(long);
2053 int hitPart = *((int*)bytes); bytes += sizeof(int);
2054 float fval = *((float*)bytes); bytes += sizeof(float);
2055 scrollbar_T *sb = gui_find_scrollbar(ident);
2058 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2059 long value = sb_info->value;
2060 long size = sb_info->size;
2061 long max = sb_info->max;
2062 BOOL isStillDragging = NO;
2063 BOOL updateKnob = YES;
2066 case NSScrollerDecrementPage:
2067 value -= (size > 2 ? size - 2 : 1);
2069 case NSScrollerIncrementPage:
2070 value += (size > 2 ? size - 2 : 1);
2072 case NSScrollerDecrementLine:
2075 case NSScrollerIncrementLine:
2078 case NSScrollerKnob:
2079 isStillDragging = YES;
2081 case NSScrollerKnobSlot:
2082 value = (long)(fval * (max - size + 1));
2089 //NSLog(@"value %d -> %d", sb_info->value, value);
2090 gui_drag_scrollbar(sb, value, isStillDragging);
2093 // Dragging the knob or option+clicking automatically updates
2094 // the knob position (on the actual NSScroller), so we only
2095 // need to set the knob position in the other cases.
2097 // Update both the left&right vertical scrollbars.
2098 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2099 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2100 [self setScrollbarThumbValue:value size:size max:max
2101 identifier:identLeft];
2102 [self setScrollbarThumbValue:value size:size max:max
2103 identifier:identRight];
2105 // Update the horizontal scrollbar.
2106 [self setScrollbarThumbValue:value size:size max:max
2113 - (void)handleSetFont:(NSData *)data
2117 const void *bytes = [data bytes];
2118 float pointSize = *((float*)bytes); bytes += sizeof(float);
2119 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2120 bytes += sizeof(unsigned); // len not used
2122 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2123 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2124 char_u *s = (char_u*)[name UTF8String];
2127 s = CONVERT_FROM_UTF8(s);
2130 set_option_value((char_u*)"guifont", 0, s, 0);
2133 CONVERT_FROM_UTF8_FREE(s);
2136 [self redrawScreen];
2139 - (void)handleDropFiles:(NSData *)data
2141 // TODO: Get rid of this method; instead use Vim script directly. At the
2142 // moment I know how to do this to open files in tabs, but I'm not sure how
2143 // to add the filenames to the command line when in command line mode.
2147 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2150 id obj = [args objectForKey:@"forceOpen"];
2151 BOOL forceOpen = YES;
2153 forceOpen = [obj boolValue];
2155 NSArray *filenames = [args objectForKey:@"filenames"];
2156 if (!(filenames && [filenames count] > 0)) return;
2159 if (!forceOpen && (State & CMDLINE)) {
2160 // HACK! If Vim is in command line mode then the files names
2161 // should be added to the command line, instead of opening the
2162 // files in tabs (unless forceOpen is set). This is taken care of by
2163 // gui_handle_drop().
2164 int n = [filenames count];
2165 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2168 for (i = 0; i < n; ++i)
2169 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2171 // NOTE! This function will free 'fnames'.
2172 // HACK! It is assumed that the 'x' and 'y' arguments are
2173 // unused when in command line mode.
2174 gui_handle_drop(0, 0, 0, fnames, n);
2179 [self handleOpenWithArguments:args];
2183 - (void)handleDropString:(NSData *)data
2188 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2189 const void *bytes = [data bytes];
2190 int len = *((int*)bytes); bytes += sizeof(int);
2191 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2193 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2194 NSRange range = { 0, [string length] };
2195 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2196 withString:@"\x0a" options:0
2199 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2200 options:0 range:range];
2203 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2204 char_u *s = (char_u*)[string UTF8String];
2206 if (input_conv.vc_type != CONV_NONE)
2207 s = string_convert(&input_conv, s, &len);
2209 dnd_yank_drag_data(s, len);
2211 if (input_conv.vc_type != CONV_NONE)
2214 add_to_input_buf(dropkey, sizeof(dropkey));
2218 - (void)startOdbEditWithArguments:(NSDictionary *)args
2220 #ifdef FEAT_ODB_EDITOR
2221 id obj = [args objectForKey:@"remoteID"];
2224 OSType serverID = [obj unsignedIntValue];
2225 NSString *remotePath = [args objectForKey:@"remotePath"];
2227 NSAppleEventDescriptor *token = nil;
2228 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2229 obj = [args objectForKey:@"remoteTokenDescType"];
2230 if (tokenData && obj) {
2231 DescType tokenType = [obj unsignedLongValue];
2232 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2236 NSArray *filenames = [args objectForKey:@"filenames"];
2237 unsigned i, numFiles = [filenames count];
2238 for (i = 0; i < numFiles; ++i) {
2239 NSString *filename = [filenames objectAtIndex:i];
2240 char_u *s = [filename vimStringSave];
2241 buf_T *buf = buflist_findname(s);
2245 if (buf->b_odb_token) {
2246 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2247 buf->b_odb_token = NULL;
2250 if (buf->b_odb_fname) {
2251 vim_free(buf->b_odb_fname);
2252 buf->b_odb_fname = NULL;
2255 buf->b_odb_server_id = serverID;
2258 buf->b_odb_token = [token retain];
2260 buf->b_odb_fname = [remotePath vimStringSave];
2262 NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2266 #endif // FEAT_ODB_EDITOR
2269 - (void)handleXcodeMod:(NSData *)data
2272 const void *bytes = [data bytes];
2273 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2274 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2278 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2279 descriptorWithDescriptorType:type
2285 - (void)handleOpenWithArguments:(NSDictionary *)args
2287 // ARGUMENT: DESCRIPTION:
2288 // -------------------------------------------------------------
2289 // filenames list of filenames
2290 // dontOpen don't open files specified in above argument
2291 // layout which layout to use to open files
2292 // selectionRange range of lines to select
2293 // searchText string to search for
2294 // cursorLine line to position the cursor on
2295 // cursorColumn column to position the cursor on
2296 // (only valid when "cursorLine" is set)
2297 // remoteID ODB parameter
2298 // remotePath ODB parameter
2299 // remoteTokenDescType ODB parameter
2300 // remoteTokenData ODB parameter
2302 //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2304 NSArray *filenames = [args objectForKey:@"filenames"];
2305 int i, numFiles = filenames ? [filenames count] : 0;
2306 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2307 int layout = [[args objectForKey:@"layout"] intValue];
2309 // Change to directory of first file to open if this is an "unused" editor
2310 // (but do not do this if editing remotely).
2311 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2312 && (starting || [self unusedEditor]) ) {
2313 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2319 // When Vim is starting we simply add the files to be opened to the
2320 // global arglist and Vim will take care of opening them for us.
2321 if (openFiles && numFiles > 0) {
2322 for (i = 0; i < numFiles; i++) {
2323 NSString *fname = [filenames objectAtIndex:i];
2326 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2327 || (p = [fname vimStringSave]) == NULL)
2328 exit(2); // See comment in -[MMBackend exit]
2330 alist_add(&global_alist, p, 2);
2333 // Vim will take care of arranging the files added to the arglist
2334 // in windows or tabs; all we must do is to specify which layout to
2336 initialWindowLayout = layout;
2339 // When Vim is already open we resort to some trickery to open the
2340 // files with the specified layout.
2342 // TODO: Figure out a better way to handle this?
2343 if (openFiles && numFiles > 0) {
2344 BOOL oneWindowInTab = topframe ? YES
2345 : (topframe->fr_layout == FR_LEAF);
2346 BOOL bufChanged = NO;
2347 BOOL bufHasFilename = NO;
2349 bufChanged = curbufIsChanged();
2350 bufHasFilename = curbuf->b_ffname != NULL;
2353 // Temporarily disable flushing since the following code may
2354 // potentially cause multiple redraws.
2355 flushDisabled = YES;
2357 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2358 if (WIN_TABS == layout && !onlyOneTab) {
2359 // By going to the last tabpage we ensure that the new tabs
2360 // will appear last (if this call is left out, the taborder
2365 // Make sure we're in normal mode first.
2366 [self addInput:@"<C-\\><C-N>"];
2369 // With "split layout" we open a new tab before opening
2370 // multiple files if the current tab has more than one window
2371 // or if there is exactly one window but whose buffer has a
2372 // filename. (The :drop command ensures modified buffers get
2373 // their own window.)
2374 if ((WIN_HOR == layout || WIN_VER == layout) &&
2375 (!oneWindowInTab || bufHasFilename))
2376 [self addInput:@":tabnew<CR>"];
2378 // The files are opened by constructing a ":drop ..." command
2379 // and executing it.
2380 NSMutableString *cmd = (WIN_TABS == layout)
2381 ? [NSMutableString stringWithString:@":tab drop"]
2382 : [NSMutableString stringWithString:@":drop"];
2384 for (i = 0; i < numFiles; ++i) {
2385 NSString *file = [filenames objectAtIndex:i];
2386 file = [file stringByEscapingSpecialFilenameCharacters];
2387 [cmd appendString:@" "];
2388 [cmd appendString:file];
2391 // Temporarily clear 'suffixes' so that the files are opened in
2392 // the same order as they appear in the "filenames" array.
2393 [self addInput:@":let t:mvim_oldsu=&su|set su=<CR>"];
2395 [self addInput:cmd];
2397 // Split the view into multiple windows if requested.
2398 if (WIN_HOR == layout)
2399 [self addInput:@"|sall"];
2400 else if (WIN_VER == layout)
2401 [self addInput:@"|vert sall"];
2403 // Restore the old value of 'suffixes'.
2404 [self addInput:@"|let &su=t:mvim_oldsu|unlet t:mvim_oldsu"];
2406 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2407 [self addInput:@"|redr|f<CR>"];
2409 // When opening one file we try to reuse the current window,
2410 // but not if its buffer is modified or has a filename.
2411 // However, the 'arglist' layout always opens the file in the
2413 NSString *file = [[filenames lastObject]
2414 stringByEscapingSpecialFilenameCharacters];
2416 if (WIN_HOR == layout) {
2417 if (!(bufHasFilename || bufChanged))
2418 cmd = [NSString stringWithFormat:@":e %@", file];
2420 cmd = [NSString stringWithFormat:@":sp %@", file];
2421 } else if (WIN_VER == layout) {
2422 if (!(bufHasFilename || bufChanged))
2423 cmd = [NSString stringWithFormat:@":e %@", file];
2425 cmd = [NSString stringWithFormat:@":vsp %@", file];
2426 } else if (WIN_TABS == layout) {
2427 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2428 cmd = [NSString stringWithFormat:@":e %@", file];
2430 cmd = [NSString stringWithFormat:@":tabe %@", file];
2432 // (The :drop command will split if there is a modified
2434 cmd = [NSString stringWithFormat:@":drop %@", file];
2437 [self addInput:cmd];
2439 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2440 [self addInput:@"|redr|f<CR>"];
2443 // Force screen redraw (does it have to be this complicated?).
2444 // (This code was taken from the end of gui_handle_drop().)
2445 update_screen(NOT_VALID);
2448 gui_update_cursor(FALSE, FALSE);
2455 if ([args objectForKey:@"remoteID"]) {
2456 // NOTE: We have to delay processing any ODB related arguments since
2457 // the file(s) may not be opened until the input buffer is processed.
2458 [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2463 NSString *lineString = [args objectForKey:@"cursorLine"];
2464 if (lineString && [lineString intValue] > 0) {
2465 NSString *columnString = [args objectForKey:@"cursorColumn"];
2466 if (!(columnString && [columnString intValue] > 0))
2467 columnString = @"1";
2469 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2470 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2471 [self addInput:cmd];
2474 NSString *rangeString = [args objectForKey:@"selectionRange"];
2476 // Build a command line string that will select the given range of
2477 // lines. If range.length == 0, then position the cursor on the given
2478 // line but do not select.
2479 NSRange range = NSRangeFromString(rangeString);
2481 if (range.length > 0) {
2482 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2483 NSMaxRange(range), range.location];
2485 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2489 [self addInput:cmd];
2492 NSString *searchText = [args objectForKey:@"searchText"];
2494 // TODO: Searching is an exclusive motion, so if the pattern would
2495 // match on row 0 column 0 then this pattern will miss that match.
2496 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@<CR>",
2501 - (BOOL)checkForModifiedBuffers
2504 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2505 if (bufIsChanged(buf)) {
2513 - (void)addInput:(NSString *)input
2515 char_u *s = (char_u*)[input UTF8String];
2518 s = CONVERT_FROM_UTF8(s);
2521 server_to_input_buf(s);
2524 CONVERT_FROM_UTF8_FREE(s);
2528 - (BOOL)unusedEditor
2530 BOOL oneWindowInTab = topframe ? YES
2531 : (topframe->fr_layout == FR_LEAF);
2532 BOOL bufChanged = NO;
2533 BOOL bufHasFilename = NO;
2535 bufChanged = curbufIsChanged();
2536 bufHasFilename = curbuf->b_ffname != NULL;
2539 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2541 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2544 - (void)redrawScreen
2546 // Force screen redraw (does it have to be this complicated?).
2547 redraw_all_later(CLEAR);
2548 update_screen(NOT_VALID);
2551 gui_update_cursor(FALSE, FALSE);
2553 // HACK! The cursor is not put back at the command line by the above
2554 // "redraw commands". The following test seems to do the trick though.
2555 if (State & CMDLINE)
2559 - (void)handleFindReplace:(NSDictionary *)args
2563 NSString *findString = [args objectForKey:@"find"];
2564 if (!findString) return;
2566 char_u *find = [findString vimStringSave];
2567 char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2568 int flags = [[args objectForKey:@"flags"] intValue];
2570 // NOTE: The flag 0x100 is used to indicate a backward search.
2571 gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2577 @end // MMBackend (Private)
2582 @implementation MMBackend (ClientServer)
2584 - (NSString *)connectionNameFromServerName:(NSString *)name
2586 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2588 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2592 - (NSConnection *)connectionForServerName:(NSString *)name
2594 // TODO: Try 'name%d' if 'name' fails.
2595 NSString *connName = [self connectionNameFromServerName:name];
2596 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2599 svrConn = [NSConnection connectionWithRegisteredName:connName
2601 // Try alternate server...
2602 if (!svrConn && alternateServerName) {
2603 //NSLog(@" trying to connect to alternate server: %@",
2604 // alternateServerName);
2605 connName = [self connectionNameFromServerName:alternateServerName];
2606 svrConn = [NSConnection connectionWithRegisteredName:connName
2610 // Try looking for alternate servers...
2612 //NSLog(@" looking for alternate servers...");
2613 NSString *alt = [self alternateServerNameForName:name];
2614 if (alt != alternateServerName) {
2615 //NSLog(@" found alternate server: %@", string);
2616 [alternateServerName release];
2617 alternateServerName = [alt copy];
2621 // Try alternate server again...
2622 if (!svrConn && alternateServerName) {
2623 //NSLog(@" trying to connect to alternate server: %@",
2624 // alternateServerName);
2625 connName = [self connectionNameFromServerName:alternateServerName];
2626 svrConn = [NSConnection connectionWithRegisteredName:connName
2631 [connectionNameDict setObject:svrConn forKey:connName];
2633 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2634 [[NSNotificationCenter defaultCenter] addObserver:self
2635 selector:@selector(serverConnectionDidDie:)
2636 name:NSConnectionDidDieNotification object:svrConn];
2643 - (NSConnection *)connectionForServerPort:(int)port
2646 NSEnumerator *e = [connectionNameDict objectEnumerator];
2648 while ((conn = [e nextObject])) {
2649 // HACK! Assume connection uses mach ports.
2650 if (port == [(NSMachPort*)[conn sendPort] machPort])
2657 - (void)serverConnectionDidDie:(NSNotification *)notification
2659 //NSLog(@"%s%@", _cmd, notification);
2661 NSConnection *svrConn = [notification object];
2663 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2664 [[NSNotificationCenter defaultCenter]
2666 name:NSConnectionDidDieNotification
2669 [connectionNameDict removeObjectsForKeys:
2670 [connectionNameDict allKeysForObject:svrConn]];
2672 // HACK! Assume connection uses mach ports.
2673 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2674 NSNumber *key = [NSNumber numberWithInt:port];
2676 [clientProxyDict removeObjectForKey:key];
2677 [serverReplyDict removeObjectForKey:key];
2680 - (void)addClient:(NSDistantObject *)client
2682 NSConnection *conn = [client connectionForProxy];
2683 // HACK! Assume connection uses mach ports.
2684 int port = [(NSMachPort*)[conn sendPort] machPort];
2685 NSNumber *key = [NSNumber numberWithInt:port];
2687 if (![clientProxyDict objectForKey:key]) {
2688 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2689 [clientProxyDict setObject:client forKey:key];
2692 // NOTE: 'clientWindow' is a global variable which is used by <client>
2693 clientWindow = port;
2696 - (NSString *)alternateServerNameForName:(NSString *)name
2698 if (!(name && [name length] > 0))
2701 // Only look for alternates if 'name' doesn't end in a digit.
2702 unichar lastChar = [name characterAtIndex:[name length]-1];
2703 if (lastChar >= '0' && lastChar <= '9')
2706 // Look for alternates among all current servers.
2707 NSArray *list = [self serverList];
2708 if (!(list && [list count] > 0))
2711 // Filter out servers starting with 'name' and ending with a number. The
2712 // (?i) pattern ensures that the match is case insensitive.
2713 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2714 NSPredicate *pred = [NSPredicate predicateWithFormat:
2715 @"SELF MATCHES %@", pat];
2716 list = [list filteredArrayUsingPredicate:pred];
2717 if ([list count] > 0) {
2718 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2719 return [list objectAtIndex:0];
2725 @end // MMBackend (ClientServer)
2730 @implementation NSString (MMServerNameCompare)
2731 - (NSComparisonResult)serverNameCompare:(NSString *)string
2733 return [self compare:string
2734 options:NSCaseInsensitiveSearch|NSNumericSearch];
2741 static int eventModifierFlagsToVimModMask(int modifierFlags)
2745 if (modifierFlags & NSShiftKeyMask)
2746 modMask |= MOD_MASK_SHIFT;
2747 if (modifierFlags & NSControlKeyMask)
2748 modMask |= MOD_MASK_CTRL;
2749 if (modifierFlags & NSAlternateKeyMask)
2750 modMask |= MOD_MASK_ALT;
2751 if (modifierFlags & NSCommandKeyMask)
2752 modMask |= MOD_MASK_CMD;
2757 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2761 if (modifierFlags & NSShiftKeyMask)
2762 modMask |= MOUSE_SHIFT;
2763 if (modifierFlags & NSControlKeyMask)
2764 modMask |= MOUSE_CTRL;
2765 if (modifierFlags & NSAlternateKeyMask)
2766 modMask |= MOUSE_ALT;
2771 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2773 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2775 return (buttonNumber >= 0 && buttonNumber < 3)
2776 ? mouseButton[buttonNumber] : -1;
2781 // This function is modeled after the VimToPython function found in if_python.c
2782 // NB This does a deep copy by value, it does not lookup references like the
2783 // VimToPython function does. This is because I didn't want to deal with the
2784 // retain cycles that this would create, and we can cover 99% of the use cases
2785 // by ignoring it. If we ever switch to using GC in MacVim then this
2786 // functionality can be implemented easily.
2787 static id vimToCocoa(typval_T * tv, int depth)
2793 // Avoid infinite recursion
2798 if (tv->v_type == VAR_STRING) {
2799 char_u * val = tv->vval.v_string;
2800 // val can be NULL if the string is empty
2802 result = [NSString string];
2805 val = CONVERT_TO_UTF8(val);
2807 result = [NSString stringWithUTF8String:(char*)val];
2809 CONVERT_TO_UTF8_FREE(val);
2812 } else if (tv->v_type == VAR_NUMBER) {
2813 // looks like sizeof(varnumber_T) is always <= sizeof(long)
2814 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2815 } else if (tv->v_type == VAR_LIST) {
2816 list_T * list = tv->vval.v_list;
2819 NSMutableArray * arr = result = [NSMutableArray array];
2822 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2823 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2824 [arr addObject:newObj];
2827 } else if (tv->v_type == VAR_DICT) {
2828 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2830 if (tv->vval.v_dict != NULL) {
2831 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2832 int todo = ht->ht_used;
2836 for (hi = ht->ht_array; todo > 0; ++hi) {
2837 if (!HASHITEM_EMPTY(hi)) {
2840 di = dict_lookup(hi);
2841 newObj = vimToCocoa(&di->di_tv, depth + 1);
2843 char_u * keyval = hi->hi_key;
2845 keyval = CONVERT_TO_UTF8(keyval);
2847 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2849 CONVERT_TO_UTF8_FREE(keyval);
2851 [dict setObject:newObj forKey:key];
2855 } else { // only func refs should fall into this category?
2863 // This function is modeled after eval_client_expr_to_string found in main.c
2864 // Returns nil if there was an error evaluating the expression, and writes a
2865 // message to errorStr.
2866 // TODO Get the error that occurred while evaluating the expression in vim
2868 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2871 char_u *s = (char_u*)[expr UTF8String];
2874 s = CONVERT_FROM_UTF8(s);
2877 int save_dbl = debug_break_level;
2878 int save_ro = redir_off;
2880 debug_break_level = -1;
2884 typval_T * tvres = eval_expr(s, NULL);
2886 debug_break_level = save_dbl;
2887 redir_off = save_ro;
2894 CONVERT_FROM_UTF8_FREE(s);
2899 gui_update_cursor(FALSE, FALSE);
2902 if (tvres == NULL) {
2904 *errstr = @"Expression evaluation failed.";
2907 id res = vimToCocoa(tvres, 1);
2912 *errstr = @"Conversion to cocoa values failed.";
2920 @implementation NSString (VimStrings)
2922 + (id)stringWithVimString:(char_u *)s
2924 // This method ensures a non-nil string is returned. If 's' cannot be
2925 // converted to a utf-8 string it is assumed to be latin-1. If conversion
2926 // still fails an empty NSString is returned.
2927 NSString *string = nil;
2930 s = CONVERT_TO_UTF8(s);
2932 string = [NSString stringWithUTF8String:(char*)s];
2934 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2936 string = [NSString stringWithCString:(char*)s
2937 encoding:NSISOLatin1StringEncoding];
2940 CONVERT_TO_UTF8_FREE(s);
2944 return string != nil ? string : [NSString string];
2947 - (char_u *)vimStringSave
2949 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2952 s = CONVERT_FROM_UTF8(s);
2954 ret = vim_strsave(s);
2956 CONVERT_FROM_UTF8_FREE(s);
2962 @end // NSString (VimStrings)