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);
511 NSLog(@"outputQueue(len:%d)=%@", [outputQueue count]/2,
513 if (![connection isValid]) {
514 NSLog(@"WARNING! Connection is invalid, exit now!");
515 NSLog(@"waitForAck=%d got_int=%d isTerminating=%d",
516 waitForAck, got_int, isTerminating);
521 [outputQueue removeAllObjects];
525 - (BOOL)waitForInput:(int)milliseconds
527 // Return NO if we timed out waiting for input, otherwise return YES.
528 BOOL inputReceived = NO;
530 // Only start the run loop if the input queue is empty, otherwise process
531 // the input first so that the input on queue isn't delayed.
532 if ([inputQueue count]) {
535 // Wait for the specified amount of time, unless 'milliseconds' is
536 // negative in which case we wait "forever" (1e6 seconds translates to
537 // approximately 11 days).
538 CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
540 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
541 == kCFRunLoopRunHandledSource) {
542 // In order to ensure that all input on the run-loop has been
543 // processed we set the timeout to 0 and keep processing until the
544 // run-loop times out.
550 // The above calls may have placed messages on the input queue so process
551 // it now. This call may enter a blocking loop.
552 if ([inputQueue count] > 0)
553 [self processInputQueue];
555 return inputReceived;
560 // NOTE: This is called if mch_exit() is called. Since we assume here that
561 // the process has started properly, be sure to use exit() instead of
562 // mch_exit() to prematurely terminate a process.
564 // To notify MacVim that this Vim process is exiting we could simply
565 // invalidate the connection and it would automatically receive a
566 // connectionDidDie: notification. However, this notification seems to
567 // take up to 300 ms to arrive which is quite a noticeable delay. Instead
568 // we immediately send a message to MacVim asking it to close the window
569 // belonging to this process, and then we invalidate the connection (in
570 // case the message got lost).
572 // Make sure no connectionDidDie: notification is received now that we are
574 [[NSNotificationCenter defaultCenter] removeObserver:self];
576 if ([connection isValid]) {
578 // Flush the entire queue in case a VimLeave autocommand added
579 // something to the queue.
580 [self queueMessage:CloseWindowMsgID data:nil];
581 [frontendProxy processCommandQueue:outputQueue];
583 @catch (NSException *e) {
584 NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
587 [connection invalidate];
590 #ifdef MAC_CLIENTSERVER
591 // The default connection is used for the client/server code.
592 [[NSConnection defaultConnection] setRootObject:nil];
593 [[NSConnection defaultConnection] invalidate];
596 if (fontContainerRef) {
597 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
598 fontContainerRef = 0;
601 usleep(MMExitProcessDelay);
604 - (void)selectTab:(int)index
606 //NSLog(@"%s%d", _cmd, index);
609 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
610 [self queueMessage:SelectTabMsgID data:data];
615 //NSLog(@"%s", _cmd);
617 NSMutableData *data = [NSMutableData data];
619 int idx = tabpage_index(curtab) - 1;
620 [data appendBytes:&idx length:sizeof(int)];
623 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
624 // This function puts the label of the tab in the global 'NameBuff'.
625 get_tabline_label(tp, FALSE);
626 char_u *s = NameBuff;
628 if (len <= 0) continue;
631 s = CONVERT_TO_UTF8(s);
634 // Count the number of windows in the tabpage.
635 //win_T *wp = tp->tp_firstwin;
637 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
639 //[data appendBytes:&wincount length:sizeof(int)];
640 [data appendBytes:&len length:sizeof(int)];
641 [data appendBytes:s length:len];
644 CONVERT_TO_UTF8_FREE(s);
648 [self queueMessage:UpdateTabBarMsgID data:data];
651 - (BOOL)tabBarVisible
653 return tabBarVisible;
656 - (void)showTabBar:(BOOL)enable
658 tabBarVisible = enable;
660 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
661 [self queueMessage:msgid data:nil];
664 - (void)setRows:(int)rows columns:(int)cols
666 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
668 int dim[] = { rows, cols };
669 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
671 [self queueMessage:SetTextDimensionsMsgID data:data];
674 - (void)setWindowTitle:(char *)title
676 NSMutableData *data = [NSMutableData data];
677 int len = strlen(title);
678 if (len <= 0) return;
680 [data appendBytes:&len length:sizeof(int)];
681 [data appendBytes:title length:len];
683 [self queueMessage:SetWindowTitleMsgID data:data];
686 - (void)setDocumentFilename:(char *)filename
688 NSMutableData *data = [NSMutableData data];
689 int len = filename ? strlen(filename) : 0;
691 [data appendBytes:&len length:sizeof(int)];
693 [data appendBytes:filename length:len];
695 [self queueMessage:SetDocumentFilenameMsgID data:data];
698 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
703 [frontendProxy showSavePanelWithAttributes:attr];
705 [self waitForDialogReturn];
707 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
708 s = [dialogReturn vimStringSave];
710 [dialogReturn release]; dialogReturn = nil;
712 @catch (NSException *e) {
713 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
719 - (oneway void)setDialogReturn:(in bycopy id)obj
721 // NOTE: This is called by
722 // - [MMVimController panelDidEnd:::], and
723 // - [MMVimController alertDidEnd:::],
724 // to indicate that a save/open panel or alert has finished.
726 // We want to distinguish between "no dialog return yet" and "dialog
727 // returned nothing". The former can be tested with dialogReturn == nil,
728 // the latter with dialogReturn == [NSNull null].
729 if (!obj) obj = [NSNull null];
731 if (obj != dialogReturn) {
732 [dialogReturn release];
733 dialogReturn = [obj retain];
737 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
742 [frontendProxy presentDialogWithAttributes:attr];
744 [self waitForDialogReturn];
746 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
747 && [dialogReturn count]) {
748 retval = [[dialogReturn objectAtIndex:0] intValue];
749 if (txtfield && [dialogReturn count] > 1) {
750 NSString *retString = [dialogReturn objectAtIndex:1];
751 char_u *ret = (char_u*)[retString UTF8String];
753 ret = CONVERT_FROM_UTF8(ret);
755 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
757 CONVERT_FROM_UTF8_FREE(ret);
762 [dialogReturn release]; dialogReturn = nil;
764 @catch (NSException *e) {
765 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
771 - (void)showToolbar:(int)enable flags:(int)flags
773 NSMutableData *data = [NSMutableData data];
775 [data appendBytes:&enable length:sizeof(int)];
776 [data appendBytes:&flags length:sizeof(int)];
778 [self queueMessage:ShowToolbarMsgID data:data];
781 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
783 NSMutableData *data = [NSMutableData data];
785 [data appendBytes:&ident length:sizeof(long)];
786 [data appendBytes:&type length:sizeof(int)];
788 [self queueMessage:CreateScrollbarMsgID data:data];
791 - (void)destroyScrollbarWithIdentifier:(long)ident
793 NSMutableData *data = [NSMutableData data];
794 [data appendBytes:&ident length:sizeof(long)];
796 [self queueMessage:DestroyScrollbarMsgID data:data];
799 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
801 NSMutableData *data = [NSMutableData data];
803 [data appendBytes:&ident length:sizeof(long)];
804 [data appendBytes:&visible length:sizeof(int)];
806 [self queueMessage:ShowScrollbarMsgID data:data];
809 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
811 NSMutableData *data = [NSMutableData data];
813 [data appendBytes:&ident length:sizeof(long)];
814 [data appendBytes:&pos length:sizeof(int)];
815 [data appendBytes:&len length:sizeof(int)];
817 [self queueMessage:SetScrollbarPositionMsgID data:data];
820 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
821 identifier:(long)ident
823 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
824 float prop = (float)size/(max+1);
825 if (fval < 0) fval = 0;
826 else if (fval > 1.0f) fval = 1.0f;
827 if (prop < 0) prop = 0;
828 else if (prop > 1.0f) prop = 1.0f;
830 NSMutableData *data = [NSMutableData data];
832 [data appendBytes:&ident length:sizeof(long)];
833 [data appendBytes:&fval length:sizeof(float)];
834 [data appendBytes:&prop length:sizeof(float)];
836 [self queueMessage:SetScrollbarThumbMsgID data:data];
839 - (void)setFont:(NSFont *)font
841 NSString *fontName = [font displayName];
842 float size = [font pointSize];
843 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
845 NSMutableData *data = [NSMutableData data];
847 [data appendBytes:&size length:sizeof(float)];
848 [data appendBytes:&len length:sizeof(int)];
849 [data appendBytes:[fontName UTF8String] length:len];
851 [self queueMessage:SetFontMsgID data:data];
855 - (void)setWideFont:(NSFont *)font
857 NSString *fontName = [font displayName];
858 float size = [font pointSize];
859 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
860 NSMutableData *data = [NSMutableData data];
862 [data appendBytes:&size length:sizeof(float)];
863 [data appendBytes:&len length:sizeof(int)];
865 [data appendBytes:[fontName UTF8String] length:len];
867 [self queueMessage:SetWideFontMsgID data:data];
870 - (void)executeActionWithName:(NSString *)name
872 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
875 NSMutableData *data = [NSMutableData data];
877 [data appendBytes:&len length:sizeof(int)];
878 [data appendBytes:[name UTF8String] length:len];
880 [self queueMessage:ExecuteActionMsgID data:data];
884 - (void)setMouseShape:(int)shape
886 NSMutableData *data = [NSMutableData data];
887 [data appendBytes:&shape length:sizeof(int)];
888 [self queueMessage:SetMouseShapeMsgID data:data];
891 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
893 // Vim specifies times in milliseconds, whereas Cocoa wants them in
895 blinkWaitInterval = .001f*wait;
896 blinkOnInterval = .001f*on;
897 blinkOffInterval = .001f*off;
903 [blinkTimer invalidate];
904 [blinkTimer release];
908 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
910 blinkState = MMBlinkStateOn;
912 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
914 selector:@selector(blinkTimerFired:)
915 userInfo:nil repeats:NO] retain];
916 gui_update_cursor(TRUE, FALSE);
917 [self flushQueue:YES];
923 if (MMBlinkStateOff == blinkState) {
924 gui_update_cursor(TRUE, FALSE);
925 [self flushQueue:YES];
928 blinkState = MMBlinkStateNone;
931 - (void)adjustLinespace:(int)linespace
933 NSMutableData *data = [NSMutableData data];
934 [data appendBytes:&linespace length:sizeof(int)];
935 [self queueMessage:AdjustLinespaceMsgID data:data];
940 [self queueMessage:ActivateMsgID data:nil];
943 - (void)setPreEditRow:(int)row column:(int)col
945 NSMutableData *data = [NSMutableData data];
946 [data appendBytes:&row length:sizeof(int)];
947 [data appendBytes:&col length:sizeof(int)];
948 [self queueMessage:SetPreEditPositionMsgID data:data];
951 - (int)lookupColorWithKey:(NSString *)key
953 if (!(key && [key length] > 0))
956 NSString *stripKey = [[[[key lowercaseString]
957 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
958 componentsSeparatedByString:@" "]
959 componentsJoinedByString:@""];
961 if (stripKey && [stripKey length] > 0) {
962 // First of all try to lookup key in the color dictionary; note that
963 // all keys in this dictionary are lowercase with no whitespace.
964 id obj = [colorDict objectForKey:stripKey];
965 if (obj) return [obj intValue];
967 // The key was not in the dictionary; is it perhaps of the form
969 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
970 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
971 [scanner setScanLocation:1];
973 if ([scanner scanHexInt:&hex]) {
978 // As a last resort, check if it is one of the system defined colors.
979 // The keys in this dictionary are also lowercase with no whitespace.
980 obj = [sysColorDict objectForKey:stripKey];
982 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
985 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
986 [col getRed:&r green:&g blue:&b alpha:&a];
987 return (((int)(r*255+.5f) & 0xff) << 16)
988 + (((int)(g*255+.5f) & 0xff) << 8)
989 + ((int)(b*255+.5f) & 0xff);
994 //NSLog(@"WARNING: No color with key %@ found.", stripKey);
998 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
1000 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1003 while ((obj = [e nextObject])) {
1004 if ([value isEqual:obj])
1011 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1013 NSMutableData *data = [NSMutableData data];
1014 [data appendBytes:&fuoptions length:sizeof(int)];
1016 [data appendBytes:&bg length:sizeof(int)];
1017 [self queueMessage:EnterFullscreenMsgID data:data];
1020 - (void)leaveFullscreen
1022 [self queueMessage:LeaveFullscreenMsgID data:nil];
1025 - (void)setFullscreenBackgroundColor:(int)color
1027 NSMutableData *data = [NSMutableData data];
1028 color = MM_COLOR(color);
1029 [data appendBytes:&color length:sizeof(int)];
1031 [self queueMessage:SetFullscreenColorMsgID data:data];
1034 - (void)setAntialias:(BOOL)antialias
1036 int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1038 [self queueMessage:msgid data:nil];
1041 - (void)updateModifiedFlag
1043 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1045 int msgid = [self checkForModifiedBuffers]
1046 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1048 [self queueMessage:msgid data:nil];
1051 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1053 // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1054 // queue is processed since that only happens in waitForInput: (and Vim
1055 // regularly checks for Ctrl-C in between waiting for input).
1056 // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1057 // which waits on the run loop will fail to detect this message (e.g. in
1058 // waitForConnectionAcknowledgement).
1060 BOOL shouldClearQueue = NO;
1061 if (InterruptMsgID == msgid) {
1062 shouldClearQueue = YES;
1064 } else if (InsertTextMsgID == msgid && data != nil) {
1065 const void *bytes = [data bytes];
1066 bytes += sizeof(int);
1067 int len = *((int*)bytes); bytes += sizeof(int);
1069 char_u *str = (char_u*)bytes;
1070 if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1071 (str[0] == intr_char && intr_char != Ctrl_C)) {
1072 shouldClearQueue = YES;
1076 } else if (TerminateNowMsgID == msgid) {
1077 shouldClearQueue = YES;
1078 isTerminating = YES;
1081 if (shouldClearQueue) {
1082 [inputQueue removeAllObjects];
1086 // Remove all previous instances of this message from the input queue, else
1087 // the input queue may fill up as a result of Vim not being able to keep up
1088 // with the speed at which new messages are received.
1089 // Keyboard input is never dropped, unless the input represents and
1090 // auto-repeated key.
1092 BOOL isKeyRepeat = NO;
1093 BOOL isKeyboardInput = NO;
1095 if (data && (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1096 CmdKeyMsgID == msgid)) {
1097 isKeyboardInput = YES;
1099 // The lowest bit of the first int is set if this key is a repeat.
1100 int flags = *((int*)[data bytes]);
1105 // Keyboard input is not removed from the queue; repeats are ignored if
1106 // there already is keyboard input on the input queue.
1107 if (isKeyRepeat || !isKeyboardInput) {
1108 int i, count = [inputQueue count];
1109 for (i = 1; i < count; i+=2) {
1110 if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1114 [inputQueue removeObjectAtIndex:i];
1115 [inputQueue removeObjectAtIndex:i-1];
1121 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1122 [inputQueue addObject:(data ? (id)data : [NSNull null])];
1125 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1127 // This is just a convenience method that allows the frontend to delay
1128 // sending messages.
1129 int i, count = [messages count];
1130 for (i = 1; i < count; i+=2)
1131 [self processInput:[[messages objectAtIndex:i-1] intValue]
1132 data:[messages objectAtIndex:i]];
1135 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1136 errorString:(out bycopy NSString **)errstr
1138 return evalExprCocoa(expr, errstr);
1142 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1144 NSString *eval = nil;
1145 char_u *s = (char_u*)[expr UTF8String];
1148 s = CONVERT_FROM_UTF8(s);
1151 char_u *res = eval_client_expr_to_string(s);
1154 CONVERT_FROM_UTF8_FREE(s);
1160 s = CONVERT_TO_UTF8(s);
1162 eval = [NSString stringWithUTF8String:(char*)s];
1164 CONVERT_TO_UTF8_FREE(s);
1172 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1174 // TODO: This method should share code with clip_mch_request_selection().
1176 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1177 // If there is no pasteboard, return YES to indicate that there is text
1182 clip_copy_selection();
1184 // Get the text to put on the pasteboard.
1185 long_u llen = 0; char_u *str = 0;
1186 int type = clip_convert_selection(&str, &llen, &clip_star);
1190 // TODO: Avoid overflow.
1191 int len = (int)llen;
1193 if (output_conv.vc_type != CONV_NONE) {
1194 char_u *conv_str = string_convert(&output_conv, str, &len);
1202 NSString *string = [[NSString alloc]
1203 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1205 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1206 [pboard declareTypes:types owner:nil];
1207 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1218 - (oneway void)addReply:(in bycopy NSString *)reply
1219 server:(in byref id <MMVimServerProtocol>)server
1221 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1223 // Replies might come at any time and in any order so we keep them in an
1224 // array inside a dictionary with the send port used as key.
1226 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1227 // HACK! Assume connection uses mach ports.
1228 int port = [(NSMachPort*)[conn sendPort] machPort];
1229 NSNumber *key = [NSNumber numberWithInt:port];
1231 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1233 replies = [NSMutableArray array];
1234 [serverReplyDict setObject:replies forKey:key];
1237 [replies addObject:reply];
1240 - (void)addInput:(in bycopy NSString *)input
1241 client:(in byref id <MMVimClientProtocol>)client
1243 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1245 [self addInput:input];
1246 [self addClient:(id)client];
1249 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1250 client:(in byref id <MMVimClientProtocol>)client
1252 [self addClient:(id)client];
1253 return [self evaluateExpression:expr];
1256 - (void)registerServerWithName:(NSString *)name
1258 NSString *svrName = name;
1259 NSConnection *svrConn = [NSConnection defaultConnection];
1262 for (i = 0; i < MMServerMax; ++i) {
1263 NSString *connName = [self connectionNameFromServerName:svrName];
1265 if ([svrConn registerName:connName]) {
1266 //NSLog(@"Registered server with name: %@", svrName);
1268 // TODO: Set request/reply time-outs to something else?
1270 // Don't wait for requests (time-out means that the message is
1272 [svrConn setRequestTimeout:0];
1273 //[svrConn setReplyTimeout:MMReplyTimeout];
1274 [svrConn setRootObject:self];
1276 // NOTE: 'serverName' is a global variable
1277 serverName = [svrName vimStringSave];
1279 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1282 need_maketitle = TRUE;
1284 [self queueMessage:SetServerNameMsgID data:
1285 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1289 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1293 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1294 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1297 // NOTE: If 'name' equals 'serverName' then the request is local (client
1298 // and server are the same). This case is not handled separately, so a
1299 // connection will be set up anyway (this simplifies the code).
1301 NSConnection *conn = [self connectionForServerName:name];
1304 char_u *s = (char_u*)[name UTF8String];
1306 s = CONVERT_FROM_UTF8(s);
1308 EMSG2(_(e_noserver), s);
1310 CONVERT_FROM_UTF8_FREE(s);
1317 // HACK! Assume connection uses mach ports.
1318 *port = [(NSMachPort*)[conn sendPort] machPort];
1321 id proxy = [conn rootProxy];
1322 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1326 NSString *eval = [proxy evaluateExpression:string client:self];
1329 *reply = [eval vimStringSave];
1331 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1338 [proxy addInput:string client:self];
1341 @catch (NSException *e) {
1342 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1349 - (NSArray *)serverList
1351 NSArray *list = nil;
1353 if ([self connection]) {
1354 id proxy = [connection rootProxy];
1355 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1358 list = [proxy serverList];
1360 @catch (NSException *e) {
1361 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1364 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1370 - (NSString *)peekForReplyOnPort:(int)port
1372 //NSLog(@"%s%d", _cmd, port);
1374 NSNumber *key = [NSNumber numberWithInt:port];
1375 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1376 if (replies && [replies count]) {
1377 //NSLog(@" %d replies, topmost is: %@", [replies count],
1378 // [replies objectAtIndex:0]);
1379 return [replies objectAtIndex:0];
1382 //NSLog(@" No replies");
1386 - (NSString *)waitForReplyOnPort:(int)port
1388 //NSLog(@"%s%d", _cmd, port);
1390 NSConnection *conn = [self connectionForServerPort:port];
1394 NSNumber *key = [NSNumber numberWithInt:port];
1395 NSMutableArray *replies = nil;
1396 NSString *reply = nil;
1398 // Wait for reply as long as the connection to the server is valid (unless
1399 // user interrupts wait with Ctrl-C).
1400 while (!got_int && [conn isValid] &&
1401 !(replies = [serverReplyDict objectForKey:key])) {
1402 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1403 beforeDate:[NSDate distantFuture]];
1407 if ([replies count] > 0) {
1408 reply = [[replies objectAtIndex:0] retain];
1409 //NSLog(@" Got reply: %@", reply);
1410 [replies removeObjectAtIndex:0];
1411 [reply autorelease];
1414 if ([replies count] == 0)
1415 [serverReplyDict removeObjectForKey:key];
1421 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1423 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1426 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1427 [client addReply:reply server:self];
1430 @catch (NSException *e) {
1431 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1434 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1445 - (void)setWaitForAck:(BOOL)yn
1450 - (void)waitForConnectionAcknowledgement
1452 if (!waitForAck) return;
1454 while (waitForAck && !got_int && [connection isValid] && !isTerminating) {
1455 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1456 beforeDate:[NSDate distantFuture]];
1457 //NSLog(@" waitForAck=%d got_int=%d isTerminating=%d isValid=%d",
1458 // waitForAck, got_int, isTerminating, [connection isValid]);
1462 // Never received a connection acknowledgement, so die.
1463 [[NSNotificationCenter defaultCenter] removeObserver:self];
1464 [frontendProxy release]; frontendProxy = nil;
1466 // NOTE: We intentionally do not call mch_exit() since this in turn
1467 // will lead to -[MMBackend exit] getting called which we want to
1469 usleep(MMExitProcessDelay);
1473 [self processInputQueue];
1476 - (oneway void)acknowledgeConnection
1478 //NSLog(@"%s", _cmd);
1486 @implementation MMBackend (Private)
1488 - (void)waitForDialogReturn
1490 // Keep processing the run loop until a dialog returns. To avoid getting
1491 // stuck in an endless loop (could happen if the setDialogReturn: message
1492 // was lost) we also do some paranoia checks.
1494 // Note that in Cocoa the user can still resize windows and select menu
1495 // items while a sheet is being displayed, so we can't just wait for the
1496 // first message to arrive and assume that is the setDialogReturn: call.
1498 while (nil == dialogReturn && !got_int && [connection isValid]
1500 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1501 beforeDate:[NSDate distantFuture]];
1503 // Search for any resize messages on the input queue. All other messages
1504 // on the input queue are dropped. The reason why we single out resize
1505 // messages is because the user may have resized the window while a sheet
1507 int i, count = [inputQueue count];
1509 id textDimData = nil;
1511 for (i = count-2; i >= 0; i -= 2) {
1512 int msgid = [[inputQueue objectAtIndex:i] intValue];
1513 if (SetTextDimensionsMsgID == msgid) {
1514 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1520 [inputQueue removeAllObjects];
1523 [inputQueue addObject:
1524 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1525 [inputQueue addObject:textDimData];
1526 [textDimData release];
1531 - (void)insertVimStateMessage
1533 // NOTE: This is the place to add Vim state that needs to be accessed from
1534 // MacVim. Do not add state that could potentially require lots of memory
1535 // since this message gets sent each time the output queue is forcibly
1536 // flushed (e.g. storing the currently selected text would be a bad idea).
1537 // We take this approach of "pushing" the state to MacVim to avoid having
1538 // to make synchronous calls from MacVim to Vim in order to get state.
1540 BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1542 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1543 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1544 [NSNumber numberWithInt:p_mh], @"p_mh",
1545 [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1546 [NSNumber numberWithBool:mmta], @"p_mmta",
1549 // Put the state before all other messages.
1550 int msgid = SetVimStateMsgID;
1551 [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1552 [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1556 - (void)processInputQueue
1558 if ([inputQueue count] == 0) return;
1560 // NOTE: One of the input events may cause this method to be called
1561 // recursively, so copy the input queue to a local variable and clear the
1562 // queue before starting to process input events (otherwise we could get
1563 // stuck in an endless loop).
1564 NSArray *q = [inputQueue copy];
1565 unsigned i, count = [q count];
1567 [inputQueue removeAllObjects];
1569 for (i = 1; i < count; i+=2) {
1570 int msgid = [[q objectAtIndex:i-1] intValue];
1571 id data = [q objectAtIndex:i];
1572 if ([data isEqual:[NSNull null]])
1575 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1576 [self handleInputEvent:msgid data:data];
1580 //NSLog(@"Clear input event queue");
1583 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1585 if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1586 CmdKeyMsgID == msgid) {
1588 const void *bytes = [data bytes];
1589 int mods = *((int*)bytes); bytes += sizeof(int);
1590 int len = *((int*)bytes); bytes += sizeof(int);
1591 NSString *key = [[NSString alloc] initWithBytes:bytes
1593 encoding:NSUTF8StringEncoding];
1594 mods = eventModifierFlagsToVimModMask(mods);
1596 if (InsertTextMsgID == msgid)
1597 [self handleInsertText:key];
1599 [self handleKeyDown:key modifiers:mods];
1602 } else if (ScrollWheelMsgID == msgid) {
1604 const void *bytes = [data bytes];
1606 int row = *((int*)bytes); bytes += sizeof(int);
1607 int col = *((int*)bytes); bytes += sizeof(int);
1608 int flags = *((int*)bytes); bytes += sizeof(int);
1609 float dy = *((float*)bytes); bytes += sizeof(float);
1611 int button = MOUSE_5;
1612 if (dy > 0) button = MOUSE_4;
1614 flags = eventModifierFlagsToVimMouseModMask(flags);
1616 int numLines = (int)round(dy);
1617 if (numLines < 0) numLines = -numLines;
1618 if (numLines == 0) numLines = 1;
1620 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1621 gui.scroll_wheel_force = numLines;
1624 gui_send_mouse_event(button, col, row, NO, flags);
1625 } else if (MouseDownMsgID == msgid) {
1627 const void *bytes = [data bytes];
1629 int row = *((int*)bytes); bytes += sizeof(int);
1630 int col = *((int*)bytes); bytes += sizeof(int);
1631 int button = *((int*)bytes); bytes += sizeof(int);
1632 int flags = *((int*)bytes); bytes += sizeof(int);
1633 int count = *((int*)bytes); bytes += sizeof(int);
1635 button = eventButtonNumberToVimMouseButton(button);
1637 flags = eventModifierFlagsToVimMouseModMask(flags);
1638 gui_send_mouse_event(button, col, row, count>1, flags);
1640 } else if (MouseUpMsgID == msgid) {
1642 const void *bytes = [data bytes];
1644 int row = *((int*)bytes); bytes += sizeof(int);
1645 int col = *((int*)bytes); bytes += sizeof(int);
1646 int flags = *((int*)bytes); bytes += sizeof(int);
1648 flags = eventModifierFlagsToVimMouseModMask(flags);
1650 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1651 } else if (MouseDraggedMsgID == msgid) {
1653 const void *bytes = [data bytes];
1655 int row = *((int*)bytes); bytes += sizeof(int);
1656 int col = *((int*)bytes); bytes += sizeof(int);
1657 int flags = *((int*)bytes); bytes += sizeof(int);
1659 flags = eventModifierFlagsToVimMouseModMask(flags);
1661 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1662 } else if (MouseMovedMsgID == msgid) {
1663 const void *bytes = [data bytes];
1664 int row = *((int*)bytes); bytes += sizeof(int);
1665 int col = *((int*)bytes); bytes += sizeof(int);
1667 gui_mouse_moved(col, row);
1668 } else if (AddInputMsgID == msgid) {
1669 NSString *string = [[NSString alloc] initWithData:data
1670 encoding:NSUTF8StringEncoding];
1672 [self addInput:string];
1675 } else if (SelectTabMsgID == msgid) {
1677 const void *bytes = [data bytes];
1678 int idx = *((int*)bytes) + 1;
1679 //NSLog(@"Selecting tab %d", idx);
1680 send_tabline_event(idx);
1681 } else if (CloseTabMsgID == msgid) {
1683 const void *bytes = [data bytes];
1684 int idx = *((int*)bytes) + 1;
1685 //NSLog(@"Closing tab %d", idx);
1686 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1687 } else if (AddNewTabMsgID == msgid) {
1688 //NSLog(@"Adding new tab");
1689 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1690 } else if (DraggedTabMsgID == msgid) {
1692 const void *bytes = [data bytes];
1693 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1695 int idx = *((int*)bytes);
1698 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1699 || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1701 const void *bytes = [data bytes];
1703 if (SetTextColumnsMsgID != msgid) {
1704 rows = *((int*)bytes); bytes += sizeof(int);
1707 if (SetTextRowsMsgID != msgid) {
1708 cols = *((int*)bytes); bytes += sizeof(int);
1712 if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1713 int dim[2] = { rows, cols };
1714 d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1715 msgid = SetTextDimensionsReplyMsgID;
1718 if (SetTextDimensionsMsgID == msgid)
1719 msgid = SetTextDimensionsReplyMsgID;
1721 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1722 // gui_resize_shell(), so we have to manually set the rows and columns
1723 // here since MacVim doesn't change the rows and columns to avoid
1724 // inconsistent states between Vim and MacVim. The message sent back
1725 // indicates that it is a reply to a message that originated in MacVim
1726 // since we need to be able to determine where a message originated.
1727 [self queueMessage:msgid data:d];
1729 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1730 gui_resize_shell(cols, rows);
1731 } else if (ExecuteMenuMsgID == msgid) {
1732 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1734 NSArray *desc = [attrs objectForKey:@"descriptor"];
1735 vimmenu_T *menu = menu_for_descriptor(desc);
1739 } else if (ToggleToolbarMsgID == msgid) {
1740 [self handleToggleToolbar];
1741 } else if (ScrollbarEventMsgID == msgid) {
1742 [self handleScrollbarEvent:data];
1743 } else if (SetFontMsgID == msgid) {
1744 [self handleSetFont:data];
1745 } else if (VimShouldCloseMsgID == msgid) {
1747 } else if (DropFilesMsgID == msgid) {
1748 [self handleDropFiles:data];
1749 } else if (DropStringMsgID == msgid) {
1750 [self handleDropString:data];
1751 } else if (GotFocusMsgID == msgid) {
1753 [self focusChange:YES];
1754 } else if (LostFocusMsgID == msgid) {
1756 [self focusChange:NO];
1757 } else if (SetMouseShapeMsgID == msgid) {
1758 const void *bytes = [data bytes];
1759 int shape = *((int*)bytes); bytes += sizeof(int);
1760 update_mouseshape(shape);
1761 } else if (XcodeModMsgID == msgid) {
1762 [self handleXcodeMod:data];
1763 } else if (OpenWithArgumentsMsgID == msgid) {
1764 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1765 } else if (FindReplaceMsgID == msgid) {
1766 [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1768 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1772 + (NSDictionary *)specialKeys
1774 static NSDictionary *specialKeys = nil;
1777 NSBundle *mainBundle = [NSBundle mainBundle];
1778 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1780 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1786 - (void)handleInsertText:(NSString *)text
1790 char_u *str = (char_u*)[text UTF8String];
1791 int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1794 char_u *conv_str = NULL;
1795 if (input_conv.vc_type != CONV_NONE) {
1796 conv_str = string_convert(&input_conv, str, &len);
1802 for (i = 0; i < len; ++i) {
1803 add_to_input_buf(str+i, 1);
1804 if (CSI == str[i]) {
1805 // NOTE: If the converted string contains the byte CSI, then it
1806 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1808 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1809 add_to_input_buf(extra, 2);
1819 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1821 // TODO: This code is a horrible mess -- clean up!
1824 char_u *chars = (char_u*)[key UTF8String];
1826 char_u *conv_str = NULL;
1828 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1830 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1831 // that new keys can easily be added.
1832 NSString *specialString = [[MMBackend specialKeys]
1834 if (specialString && [specialString length] > 1) {
1835 //NSLog(@"special key: %@", specialString);
1836 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1837 [specialString characterAtIndex:1]);
1839 ikey = simplify_key(ikey, &mods);
1844 special[1] = K_SECOND(ikey);
1845 special[2] = K_THIRD(ikey);
1849 } else if (1 == length && TAB == chars[0]) {
1850 // Tab is a trouble child:
1851 // - <Tab> is added to the input buffer as is
1852 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1853 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1854 // to be converted to utf-8
1855 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1856 // - <C-Tab> is reserved by Mac OS X
1857 // - <D-Tab> is reserved by Mac OS X
1862 if (mods & MOD_MASK_SHIFT) {
1863 mods &= ~MOD_MASK_SHIFT;
1865 special[1] = K_SECOND(K_S_TAB);
1866 special[2] = K_THIRD(K_S_TAB);
1868 } else if (mods & MOD_MASK_ALT) {
1869 int mtab = 0x80 | TAB;
1873 special[0] = (mtab >> 6) + 0xc0;
1874 special[1] = mtab & 0xbf;
1882 mods &= ~MOD_MASK_ALT;
1884 } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1885 // META key is treated separately. This code was taken from gui_w48.c
1886 // and gui_gtk_x11.c.
1888 int ch = simplify_key(chars[0], &mods);
1890 // Remove the SHIFT modifier for keys where it's already included,
1891 // e.g., '(' and '*'
1892 if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1893 mods &= ~MOD_MASK_SHIFT;
1895 // Interpret the ALT key as making the key META, include SHIFT, etc.
1896 ch = extract_modifiers(ch, &mods);
1902 string[len++] = CSI;
1903 string[len++] = KS_MODIFIER;
1904 string[len++] = mods;
1907 if (IS_SPECIAL(ch)) {
1908 string[len++] = CSI;
1909 string[len++] = K_SECOND(ch);
1910 string[len++] = K_THIRD(ch);
1914 // TODO: What if 'enc' is not "utf-8"?
1915 if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1916 string[len++] = ch & 0xbf;
1917 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1918 if (string[len-1] == CSI) {
1919 string[len++] = KS_EXTRA;
1920 string[len++] = (int)KE_CSI;
1926 add_to_input_buf(string, len);
1928 } else if (length > 0) {
1929 unichar c = [key characterAtIndex:0];
1930 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1931 // [key characterAtIndex:0], mods);
1933 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1934 // cleared since they are already added to the key by the AppKit.
1935 // Unfortunately, the only way to deal with when to clear the modifiers
1936 // or not seems to be to have hard-wired rules like this.
1937 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1938 || 0x9 == c || 0xd == c || ESC == c) ) {
1939 mods &= ~MOD_MASK_SHIFT;
1940 mods &= ~MOD_MASK_CTRL;
1941 //NSLog(@"clear shift ctrl");
1945 if (input_conv.vc_type != CONV_NONE) {
1946 conv_str = string_convert(&input_conv, chars, &length);
1953 if (chars && length > 0) {
1955 //NSLog(@"adding mods: %d", mods);
1957 modChars[1] = KS_MODIFIER;
1959 add_to_input_buf(modChars, 3);
1962 //NSLog(@"add to input buf: 0x%x", chars[0]);
1963 // TODO: Check for CSI bytes?
1964 add_to_input_buf(chars, length);
1973 - (void)queueMessage:(int)msgid data:(NSData *)data
1975 //if (msgid != EnableMenuItemMsgID)
1976 // NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1978 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1980 [outputQueue addObject:data];
1982 [outputQueue addObject:[NSData data]];
1985 - (void)connectionDidDie:(NSNotification *)notification
1987 // If the main connection to MacVim is lost this means that MacVim was
1988 // either quit (by the user chosing Quit on the MacVim menu), or it has
1989 // crashed. In the former case the flag 'isTerminating' is set and we then
1990 // quit cleanly; in the latter case we make sure the swap files are left
1993 // NOTE: This is not called if a Vim controller invalidates its connection.
1995 //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
1999 getout_preserve_modified(1);
2002 - (void)blinkTimerFired:(NSTimer *)timer
2004 NSTimeInterval timeInterval = 0;
2006 [blinkTimer release];
2009 if (MMBlinkStateOn == blinkState) {
2010 gui_undraw_cursor();
2011 blinkState = MMBlinkStateOff;
2012 timeInterval = blinkOffInterval;
2013 } else if (MMBlinkStateOff == blinkState) {
2014 gui_update_cursor(TRUE, FALSE);
2015 blinkState = MMBlinkStateOn;
2016 timeInterval = blinkOnInterval;
2019 if (timeInterval > 0) {
2021 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2022 selector:@selector(blinkTimerFired:)
2023 userInfo:nil repeats:NO] retain];
2024 [self flushQueue:YES];
2028 - (void)focusChange:(BOOL)on
2030 gui_focus_change(on);
2033 - (void)handleToggleToolbar
2035 // If 'go' contains 'T', then remove it, else add it.
2037 char_u go[sizeof(GO_ALL)+2];
2042 p = vim_strchr(go, GO_TOOLBAR);
2046 char_u *end = go + len;
2052 go[len] = GO_TOOLBAR;
2056 set_option_value((char_u*)"guioptions", 0, go, 0);
2058 [self redrawScreen];
2061 - (void)handleScrollbarEvent:(NSData *)data
2065 const void *bytes = [data bytes];
2066 long ident = *((long*)bytes); bytes += sizeof(long);
2067 int hitPart = *((int*)bytes); bytes += sizeof(int);
2068 float fval = *((float*)bytes); bytes += sizeof(float);
2069 scrollbar_T *sb = gui_find_scrollbar(ident);
2072 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2073 long value = sb_info->value;
2074 long size = sb_info->size;
2075 long max = sb_info->max;
2076 BOOL isStillDragging = NO;
2077 BOOL updateKnob = YES;
2080 case NSScrollerDecrementPage:
2081 value -= (size > 2 ? size - 2 : 1);
2083 case NSScrollerIncrementPage:
2084 value += (size > 2 ? size - 2 : 1);
2086 case NSScrollerDecrementLine:
2089 case NSScrollerIncrementLine:
2092 case NSScrollerKnob:
2093 isStillDragging = YES;
2095 case NSScrollerKnobSlot:
2096 value = (long)(fval * (max - size + 1));
2103 //NSLog(@"value %d -> %d", sb_info->value, value);
2104 gui_drag_scrollbar(sb, value, isStillDragging);
2107 // Dragging the knob or option+clicking automatically updates
2108 // the knob position (on the actual NSScroller), so we only
2109 // need to set the knob position in the other cases.
2111 // Update both the left&right vertical scrollbars.
2112 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2113 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2114 [self setScrollbarThumbValue:value size:size max:max
2115 identifier:identLeft];
2116 [self setScrollbarThumbValue:value size:size max:max
2117 identifier:identRight];
2119 // Update the horizontal scrollbar.
2120 [self setScrollbarThumbValue:value size:size max:max
2127 - (void)handleSetFont:(NSData *)data
2131 const void *bytes = [data bytes];
2132 float pointSize = *((float*)bytes); bytes += sizeof(float);
2133 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2134 bytes += sizeof(unsigned); // len not used
2136 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2137 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2138 char_u *s = (char_u*)[name UTF8String];
2141 s = CONVERT_FROM_UTF8(s);
2144 set_option_value((char_u*)"guifont", 0, s, 0);
2147 CONVERT_FROM_UTF8_FREE(s);
2150 [self redrawScreen];
2153 - (void)handleDropFiles:(NSData *)data
2155 // TODO: Get rid of this method; instead use Vim script directly. At the
2156 // moment I know how to do this to open files in tabs, but I'm not sure how
2157 // to add the filenames to the command line when in command line mode.
2161 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2164 id obj = [args objectForKey:@"forceOpen"];
2165 BOOL forceOpen = YES;
2167 forceOpen = [obj boolValue];
2169 NSArray *filenames = [args objectForKey:@"filenames"];
2170 if (!(filenames && [filenames count] > 0)) return;
2173 if (!forceOpen && (State & CMDLINE)) {
2174 // HACK! If Vim is in command line mode then the files names
2175 // should be added to the command line, instead of opening the
2176 // files in tabs (unless forceOpen is set). This is taken care of by
2177 // gui_handle_drop().
2178 int n = [filenames count];
2179 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2182 for (i = 0; i < n; ++i)
2183 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2185 // NOTE! This function will free 'fnames'.
2186 // HACK! It is assumed that the 'x' and 'y' arguments are
2187 // unused when in command line mode.
2188 gui_handle_drop(0, 0, 0, fnames, n);
2193 [self handleOpenWithArguments:args];
2197 - (void)handleDropString:(NSData *)data
2202 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2203 const void *bytes = [data bytes];
2204 int len = *((int*)bytes); bytes += sizeof(int);
2205 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2207 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2208 NSRange range = { 0, [string length] };
2209 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2210 withString:@"\x0a" options:0
2213 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2214 options:0 range:range];
2217 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2218 char_u *s = (char_u*)[string UTF8String];
2220 if (input_conv.vc_type != CONV_NONE)
2221 s = string_convert(&input_conv, s, &len);
2223 dnd_yank_drag_data(s, len);
2225 if (input_conv.vc_type != CONV_NONE)
2228 add_to_input_buf(dropkey, sizeof(dropkey));
2232 - (void)startOdbEditWithArguments:(NSDictionary *)args
2234 #ifdef FEAT_ODB_EDITOR
2235 id obj = [args objectForKey:@"remoteID"];
2238 OSType serverID = [obj unsignedIntValue];
2239 NSString *remotePath = [args objectForKey:@"remotePath"];
2241 NSAppleEventDescriptor *token = nil;
2242 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2243 obj = [args objectForKey:@"remoteTokenDescType"];
2244 if (tokenData && obj) {
2245 DescType tokenType = [obj unsignedLongValue];
2246 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2250 NSArray *filenames = [args objectForKey:@"filenames"];
2251 unsigned i, numFiles = [filenames count];
2252 for (i = 0; i < numFiles; ++i) {
2253 NSString *filename = [filenames objectAtIndex:i];
2254 char_u *s = [filename vimStringSave];
2255 buf_T *buf = buflist_findname(s);
2259 if (buf->b_odb_token) {
2260 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2261 buf->b_odb_token = NULL;
2264 if (buf->b_odb_fname) {
2265 vim_free(buf->b_odb_fname);
2266 buf->b_odb_fname = NULL;
2269 buf->b_odb_server_id = serverID;
2272 buf->b_odb_token = [token retain];
2274 buf->b_odb_fname = [remotePath vimStringSave];
2276 NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2280 #endif // FEAT_ODB_EDITOR
2283 - (void)handleXcodeMod:(NSData *)data
2286 const void *bytes = [data bytes];
2287 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2288 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2292 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2293 descriptorWithDescriptorType:type
2299 - (void)handleOpenWithArguments:(NSDictionary *)args
2301 // ARGUMENT: DESCRIPTION:
2302 // -------------------------------------------------------------
2303 // filenames list of filenames
2304 // dontOpen don't open files specified in above argument
2305 // layout which layout to use to open files
2306 // selectionRange range of lines to select
2307 // searchText string to search for
2308 // cursorLine line to position the cursor on
2309 // cursorColumn column to position the cursor on
2310 // (only valid when "cursorLine" is set)
2311 // remoteID ODB parameter
2312 // remotePath ODB parameter
2313 // remoteTokenDescType ODB parameter
2314 // remoteTokenData ODB parameter
2316 //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2318 NSArray *filenames = [args objectForKey:@"filenames"];
2319 int i, numFiles = filenames ? [filenames count] : 0;
2320 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2321 int layout = [[args objectForKey:@"layout"] intValue];
2323 // Change to directory of first file to open if this is an "unused" editor
2324 // (but do not do this if editing remotely).
2325 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2326 && (starting || [self unusedEditor]) ) {
2327 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2333 // When Vim is starting we simply add the files to be opened to the
2334 // global arglist and Vim will take care of opening them for us.
2335 if (openFiles && numFiles > 0) {
2336 for (i = 0; i < numFiles; i++) {
2337 NSString *fname = [filenames objectAtIndex:i];
2340 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2341 || (p = [fname vimStringSave]) == NULL)
2342 exit(2); // See comment in -[MMBackend exit]
2344 alist_add(&global_alist, p, 2);
2347 // Vim will take care of arranging the files added to the arglist
2348 // in windows or tabs; all we must do is to specify which layout to
2350 initialWindowLayout = layout;
2353 // When Vim is already open we resort to some trickery to open the
2354 // files with the specified layout.
2356 // TODO: Figure out a better way to handle this?
2357 if (openFiles && numFiles > 0) {
2358 BOOL oneWindowInTab = topframe ? YES
2359 : (topframe->fr_layout == FR_LEAF);
2360 BOOL bufChanged = NO;
2361 BOOL bufHasFilename = NO;
2363 bufChanged = curbufIsChanged();
2364 bufHasFilename = curbuf->b_ffname != NULL;
2367 // Temporarily disable flushing since the following code may
2368 // potentially cause multiple redraws.
2369 flushDisabled = YES;
2371 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2372 if (WIN_TABS == layout && !onlyOneTab) {
2373 // By going to the last tabpage we ensure that the new tabs
2374 // will appear last (if this call is left out, the taborder
2379 // Make sure we're in normal mode first.
2380 [self addInput:@"<C-\\><C-N>"];
2383 // With "split layout" we open a new tab before opening
2384 // multiple files if the current tab has more than one window
2385 // or if there is exactly one window but whose buffer has a
2386 // filename. (The :drop command ensures modified buffers get
2387 // their own window.)
2388 if ((WIN_HOR == layout || WIN_VER == layout) &&
2389 (!oneWindowInTab || bufHasFilename))
2390 [self addInput:@":tabnew<CR>"];
2392 // The files are opened by constructing a ":drop ..." command
2393 // and executing it.
2394 NSMutableString *cmd = (WIN_TABS == layout)
2395 ? [NSMutableString stringWithString:@":tab drop"]
2396 : [NSMutableString stringWithString:@":drop"];
2398 for (i = 0; i < numFiles; ++i) {
2399 NSString *file = [filenames objectAtIndex:i];
2400 file = [file stringByEscapingSpecialFilenameCharacters];
2401 [cmd appendString:@" "];
2402 [cmd appendString:file];
2405 // Temporarily clear 'suffixes' so that the files are opened in
2406 // the same order as they appear in the "filenames" array.
2407 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2409 [self addInput:cmd];
2411 // Split the view into multiple windows if requested.
2412 if (WIN_HOR == layout)
2413 [self addInput:@"|sall"];
2414 else if (WIN_VER == layout)
2415 [self addInput:@"|vert sall"];
2417 // Restore the old value of 'suffixes'.
2418 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu"];
2420 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2421 [self addInput:@"|redr|f<CR>"];
2423 // When opening one file we try to reuse the current window,
2424 // but not if its buffer is modified or has a filename.
2425 // However, the 'arglist' layout always opens the file in the
2427 NSString *file = [[filenames lastObject]
2428 stringByEscapingSpecialFilenameCharacters];
2430 if (WIN_HOR == layout) {
2431 if (!(bufHasFilename || bufChanged))
2432 cmd = [NSString stringWithFormat:@":e %@", file];
2434 cmd = [NSString stringWithFormat:@":sp %@", file];
2435 } else if (WIN_VER == layout) {
2436 if (!(bufHasFilename || bufChanged))
2437 cmd = [NSString stringWithFormat:@":e %@", file];
2439 cmd = [NSString stringWithFormat:@":vsp %@", file];
2440 } else if (WIN_TABS == layout) {
2441 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2442 cmd = [NSString stringWithFormat:@":e %@", file];
2444 cmd = [NSString stringWithFormat:@":tabe %@", file];
2446 // (The :drop command will split if there is a modified
2448 cmd = [NSString stringWithFormat:@":drop %@", file];
2451 [self addInput:cmd];
2453 // Adding "|redr|f" ensures a "Hit ENTER" prompt is not shown.
2454 [self addInput:@"|redr|f<CR>"];
2457 // Force screen redraw (does it have to be this complicated?).
2458 // (This code was taken from the end of gui_handle_drop().)
2459 update_screen(NOT_VALID);
2462 gui_update_cursor(FALSE, FALSE);
2469 if ([args objectForKey:@"remoteID"]) {
2470 // NOTE: We have to delay processing any ODB related arguments since
2471 // the file(s) may not be opened until the input buffer is processed.
2472 [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2477 NSString *lineString = [args objectForKey:@"cursorLine"];
2478 if (lineString && [lineString intValue] > 0) {
2479 NSString *columnString = [args objectForKey:@"cursorColumn"];
2480 if (!(columnString && [columnString intValue] > 0))
2481 columnString = @"1";
2483 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2484 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2485 [self addInput:cmd];
2488 NSString *rangeString = [args objectForKey:@"selectionRange"];
2490 // Build a command line string that will select the given range of
2491 // lines. If range.length == 0, then position the cursor on the given
2492 // line but do not select.
2493 NSRange range = NSRangeFromString(rangeString);
2495 if (range.length > 0) {
2496 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2497 NSMaxRange(range), range.location];
2499 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2503 [self addInput:cmd];
2506 NSString *searchText = [args objectForKey:@"searchText"];
2508 // TODO: Searching is an exclusive motion, so if the pattern would
2509 // match on row 0 column 0 then this pattern will miss that match.
2510 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@<CR>",
2515 - (BOOL)checkForModifiedBuffers
2518 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2519 if (bufIsChanged(buf)) {
2527 - (void)addInput:(NSString *)input
2529 char_u *s = (char_u*)[input UTF8String];
2532 s = CONVERT_FROM_UTF8(s);
2535 server_to_input_buf(s);
2538 CONVERT_FROM_UTF8_FREE(s);
2542 - (BOOL)unusedEditor
2544 BOOL oneWindowInTab = topframe ? YES
2545 : (topframe->fr_layout == FR_LEAF);
2546 BOOL bufChanged = NO;
2547 BOOL bufHasFilename = NO;
2549 bufChanged = curbufIsChanged();
2550 bufHasFilename = curbuf->b_ffname != NULL;
2553 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2555 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2558 - (void)redrawScreen
2560 // Force screen redraw (does it have to be this complicated?).
2561 redraw_all_later(CLEAR);
2562 update_screen(NOT_VALID);
2565 gui_update_cursor(FALSE, FALSE);
2567 // HACK! The cursor is not put back at the command line by the above
2568 // "redraw commands". The following test seems to do the trick though.
2569 if (State & CMDLINE)
2573 - (void)handleFindReplace:(NSDictionary *)args
2577 NSString *findString = [args objectForKey:@"find"];
2578 if (!findString) return;
2580 char_u *find = [findString vimStringSave];
2581 char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2582 int flags = [[args objectForKey:@"flags"] intValue];
2584 // NOTE: The flag 0x100 is used to indicate a backward search.
2585 gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2591 @end // MMBackend (Private)
2596 @implementation MMBackend (ClientServer)
2598 - (NSString *)connectionNameFromServerName:(NSString *)name
2600 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2602 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2606 - (NSConnection *)connectionForServerName:(NSString *)name
2608 // TODO: Try 'name%d' if 'name' fails.
2609 NSString *connName = [self connectionNameFromServerName:name];
2610 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2613 svrConn = [NSConnection connectionWithRegisteredName:connName
2615 // Try alternate server...
2616 if (!svrConn && alternateServerName) {
2617 //NSLog(@" trying to connect to alternate server: %@",
2618 // alternateServerName);
2619 connName = [self connectionNameFromServerName:alternateServerName];
2620 svrConn = [NSConnection connectionWithRegisteredName:connName
2624 // Try looking for alternate servers...
2626 //NSLog(@" looking for alternate servers...");
2627 NSString *alt = [self alternateServerNameForName:name];
2628 if (alt != alternateServerName) {
2629 //NSLog(@" found alternate server: %@", string);
2630 [alternateServerName release];
2631 alternateServerName = [alt copy];
2635 // Try alternate server again...
2636 if (!svrConn && alternateServerName) {
2637 //NSLog(@" trying to connect to alternate server: %@",
2638 // alternateServerName);
2639 connName = [self connectionNameFromServerName:alternateServerName];
2640 svrConn = [NSConnection connectionWithRegisteredName:connName
2645 [connectionNameDict setObject:svrConn forKey:connName];
2647 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2648 [[NSNotificationCenter defaultCenter] addObserver:self
2649 selector:@selector(serverConnectionDidDie:)
2650 name:NSConnectionDidDieNotification object:svrConn];
2657 - (NSConnection *)connectionForServerPort:(int)port
2660 NSEnumerator *e = [connectionNameDict objectEnumerator];
2662 while ((conn = [e nextObject])) {
2663 // HACK! Assume connection uses mach ports.
2664 if (port == [(NSMachPort*)[conn sendPort] machPort])
2671 - (void)serverConnectionDidDie:(NSNotification *)notification
2673 //NSLog(@"%s%@", _cmd, notification);
2675 NSConnection *svrConn = [notification object];
2677 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2678 [[NSNotificationCenter defaultCenter]
2680 name:NSConnectionDidDieNotification
2683 [connectionNameDict removeObjectsForKeys:
2684 [connectionNameDict allKeysForObject:svrConn]];
2686 // HACK! Assume connection uses mach ports.
2687 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2688 NSNumber *key = [NSNumber numberWithInt:port];
2690 [clientProxyDict removeObjectForKey:key];
2691 [serverReplyDict removeObjectForKey:key];
2694 - (void)addClient:(NSDistantObject *)client
2696 NSConnection *conn = [client connectionForProxy];
2697 // HACK! Assume connection uses mach ports.
2698 int port = [(NSMachPort*)[conn sendPort] machPort];
2699 NSNumber *key = [NSNumber numberWithInt:port];
2701 if (![clientProxyDict objectForKey:key]) {
2702 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2703 [clientProxyDict setObject:client forKey:key];
2706 // NOTE: 'clientWindow' is a global variable which is used by <client>
2707 clientWindow = port;
2710 - (NSString *)alternateServerNameForName:(NSString *)name
2712 if (!(name && [name length] > 0))
2715 // Only look for alternates if 'name' doesn't end in a digit.
2716 unichar lastChar = [name characterAtIndex:[name length]-1];
2717 if (lastChar >= '0' && lastChar <= '9')
2720 // Look for alternates among all current servers.
2721 NSArray *list = [self serverList];
2722 if (!(list && [list count] > 0))
2725 // Filter out servers starting with 'name' and ending with a number. The
2726 // (?i) pattern ensures that the match is case insensitive.
2727 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2728 NSPredicate *pred = [NSPredicate predicateWithFormat:
2729 @"SELF MATCHES %@", pat];
2730 list = [list filteredArrayUsingPredicate:pred];
2731 if ([list count] > 0) {
2732 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2733 return [list objectAtIndex:0];
2739 @end // MMBackend (ClientServer)
2744 @implementation NSString (MMServerNameCompare)
2745 - (NSComparisonResult)serverNameCompare:(NSString *)string
2747 return [self compare:string
2748 options:NSCaseInsensitiveSearch|NSNumericSearch];
2755 static int eventModifierFlagsToVimModMask(int modifierFlags)
2759 if (modifierFlags & NSShiftKeyMask)
2760 modMask |= MOD_MASK_SHIFT;
2761 if (modifierFlags & NSControlKeyMask)
2762 modMask |= MOD_MASK_CTRL;
2763 if (modifierFlags & NSAlternateKeyMask)
2764 modMask |= MOD_MASK_ALT;
2765 if (modifierFlags & NSCommandKeyMask)
2766 modMask |= MOD_MASK_CMD;
2771 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2775 if (modifierFlags & NSShiftKeyMask)
2776 modMask |= MOUSE_SHIFT;
2777 if (modifierFlags & NSControlKeyMask)
2778 modMask |= MOUSE_CTRL;
2779 if (modifierFlags & NSAlternateKeyMask)
2780 modMask |= MOUSE_ALT;
2785 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2787 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2789 return (buttonNumber >= 0 && buttonNumber < 3)
2790 ? mouseButton[buttonNumber] : -1;
2795 // This function is modeled after the VimToPython function found in if_python.c
2796 // NB This does a deep copy by value, it does not lookup references like the
2797 // VimToPython function does. This is because I didn't want to deal with the
2798 // retain cycles that this would create, and we can cover 99% of the use cases
2799 // by ignoring it. If we ever switch to using GC in MacVim then this
2800 // functionality can be implemented easily.
2801 static id vimToCocoa(typval_T * tv, int depth)
2807 // Avoid infinite recursion
2812 if (tv->v_type == VAR_STRING) {
2813 char_u * val = tv->vval.v_string;
2814 // val can be NULL if the string is empty
2816 result = [NSString string];
2819 val = CONVERT_TO_UTF8(val);
2821 result = [NSString stringWithUTF8String:(char*)val];
2823 CONVERT_TO_UTF8_FREE(val);
2826 } else if (tv->v_type == VAR_NUMBER) {
2827 // looks like sizeof(varnumber_T) is always <= sizeof(long)
2828 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2829 } else if (tv->v_type == VAR_LIST) {
2830 list_T * list = tv->vval.v_list;
2833 NSMutableArray * arr = result = [NSMutableArray array];
2836 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2837 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2838 [arr addObject:newObj];
2841 } else if (tv->v_type == VAR_DICT) {
2842 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2844 if (tv->vval.v_dict != NULL) {
2845 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2846 int todo = ht->ht_used;
2850 for (hi = ht->ht_array; todo > 0; ++hi) {
2851 if (!HASHITEM_EMPTY(hi)) {
2854 di = dict_lookup(hi);
2855 newObj = vimToCocoa(&di->di_tv, depth + 1);
2857 char_u * keyval = hi->hi_key;
2859 keyval = CONVERT_TO_UTF8(keyval);
2861 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2863 CONVERT_TO_UTF8_FREE(keyval);
2865 [dict setObject:newObj forKey:key];
2869 } else { // only func refs should fall into this category?
2877 // This function is modeled after eval_client_expr_to_string found in main.c
2878 // Returns nil if there was an error evaluating the expression, and writes a
2879 // message to errorStr.
2880 // TODO Get the error that occurred while evaluating the expression in vim
2882 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2885 char_u *s = (char_u*)[expr UTF8String];
2888 s = CONVERT_FROM_UTF8(s);
2891 int save_dbl = debug_break_level;
2892 int save_ro = redir_off;
2894 debug_break_level = -1;
2898 typval_T * tvres = eval_expr(s, NULL);
2900 debug_break_level = save_dbl;
2901 redir_off = save_ro;
2908 CONVERT_FROM_UTF8_FREE(s);
2913 gui_update_cursor(FALSE, FALSE);
2916 if (tvres == NULL) {
2918 *errstr = @"Expression evaluation failed.";
2921 id res = vimToCocoa(tvres, 1);
2926 *errstr = @"Conversion to cocoa values failed.";
2934 @implementation NSString (VimStrings)
2936 + (id)stringWithVimString:(char_u *)s
2938 // This method ensures a non-nil string is returned. If 's' cannot be
2939 // converted to a utf-8 string it is assumed to be latin-1. If conversion
2940 // still fails an empty NSString is returned.
2941 NSString *string = nil;
2944 s = CONVERT_TO_UTF8(s);
2946 string = [NSString stringWithUTF8String:(char*)s];
2948 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2950 string = [NSString stringWithCString:(char*)s
2951 encoding:NSISOLatin1StringEncoding];
2954 CONVERT_TO_UTF8_FREE(s);
2958 return string != nil ? string : [NSString string];
2961 - (char_u *)vimStringSave
2963 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2966 s = CONVERT_FROM_UTF8(s);
2968 ret = vim_strsave(s);
2970 CONVERT_FROM_UTF8_FREE(s);
2976 @end // NSString (VimStrings)