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 [MMAppController processInput:forIdentifier:].
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);
55 vimmenu_T *menu_for_descriptor(NSArray *desc);
57 static id evalExprCocoa(NSString * expr, NSString ** errstr);
66 static NSString *MMSymlinkWarningString =
67 @"\n\n\tMost likely this is because you have symlinked directly to\n"
68 "\tthe Vim binary, which Cocoa does not allow. Please use an\n"
69 "\talias or the mvim shell script instead. If you have not used\n"
70 "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
73 extern GuiFont gui_mch_retain_font(GuiFont font);
77 @interface NSString (MMServerNameCompare)
78 - (NSComparisonResult)serverNameCompare:(NSString *)string;
84 @interface MMBackend (Private)
85 - (void)clearDrawData;
86 - (void)didChangeWholeLine;
87 - (void)waitForDialogReturn;
88 - (void)insertVimStateMessage;
89 - (void)processInputQueue;
90 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
91 + (NSDictionary *)specialKeys;
92 - (void)handleInsertText:(NSString *)text;
93 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
94 - (void)queueMessage:(int)msgid data:(NSData *)data;
95 - (void)connectionDidDie:(NSNotification *)notification;
96 - (void)blinkTimerFired:(NSTimer *)timer;
97 - (void)focusChange:(BOOL)on;
98 - (void)handleToggleToolbar;
99 - (void)handleScrollbarEvent:(NSData *)data;
100 - (void)handleSetFont:(NSData *)data;
101 - (void)handleDropFiles:(NSData *)data;
102 - (void)handleDropString:(NSData *)data;
103 - (void)startOdbEditWithArguments:(NSDictionary *)args;
104 - (void)handleXcodeMod:(NSData *)data;
105 - (void)handleOpenWithArguments:(NSDictionary *)args;
106 - (BOOL)checkForModifiedBuffers;
107 - (void)addInput:(NSString *)input;
108 - (BOOL)unusedEditor;
109 - (void)redrawScreen;
110 - (void)handleFindReplace:(NSDictionary *)args;
115 @interface MMBackend (ClientServer)
116 - (NSString *)connectionNameFromServerName:(NSString *)name;
117 - (NSConnection *)connectionForServerName:(NSString *)name;
118 - (NSConnection *)connectionForServerPort:(int)port;
119 - (void)serverConnectionDidDie:(NSNotification *)notification;
120 - (void)addClient:(NSDistantObject *)client;
121 - (NSString *)alternateServerNameForName:(NSString *)name;
126 @implementation MMBackend
128 + (MMBackend *)sharedInstance
130 static MMBackend *singleton = nil;
131 return singleton ? singleton : (singleton = [MMBackend new]);
137 if (!self) return nil;
139 outputQueue = [[NSMutableArray alloc] init];
140 inputQueue = [[NSMutableArray alloc] init];
141 drawData = [[NSMutableData alloc] initWithCapacity:1024];
142 connectionNameDict = [[NSMutableDictionary alloc] init];
143 clientProxyDict = [[NSMutableDictionary alloc] init];
144 serverReplyDict = [[NSMutableDictionary alloc] init];
146 NSBundle *mainBundle = [NSBundle mainBundle];
147 NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
149 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
151 path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
153 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
156 path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
158 actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
160 if (!(colorDict && sysColorDict && actionDict))
161 NSLog(@"ERROR: Failed to load dictionaries.%@",
162 MMSymlinkWarningString);
169 //NSLog(@"%@ %s", [self className], _cmd);
170 [[NSNotificationCenter defaultCenter] removeObserver:self];
172 gui_mch_free_font(oldWideFont); oldWideFont = NOFONT;
173 [blinkTimer release]; blinkTimer = nil;
174 [alternateServerName release]; alternateServerName = nil;
175 [serverReplyDict release]; serverReplyDict = nil;
176 [clientProxyDict release]; clientProxyDict = nil;
177 [connectionNameDict release]; connectionNameDict = nil;
178 [inputQueue release]; inputQueue = nil;
179 [outputQueue release]; outputQueue = nil;
180 [drawData release]; drawData = nil;
181 [connection release]; connection = nil;
182 [actionDict release]; actionDict = nil;
183 [sysColorDict release]; sysColorDict = nil;
184 [colorDict release]; colorDict = nil;
189 - (void)setBackgroundColor:(int)color
191 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
194 - (void)setForegroundColor:(int)color
196 foregroundColor = MM_COLOR(color);
199 - (void)setSpecialColor:(int)color
201 specialColor = MM_COLOR(color);
204 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
206 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
207 defaultForegroundColor = MM_COLOR(fg);
209 NSMutableData *data = [NSMutableData data];
211 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
212 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
214 [self queueMessage:SetDefaultColorsMsgID data:data];
217 - (NSConnection *)connection
220 // NOTE! If the name of the connection changes here it must also be
221 // updated in MMAppController.m.
222 NSString *name = [NSString stringWithFormat:@"%@-connection",
223 [[NSBundle mainBundle] bundlePath]];
225 connection = [NSConnection connectionWithRegisteredName:name host:nil];
229 // NOTE: 'connection' may be nil here.
233 - (NSDictionary *)actionDict
238 - (int)initialWindowLayout
240 return initialWindowLayout;
243 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
245 [self queueMessage:msgid data:[props dictionaryAsData]];
250 if (![self connection]) {
252 // This is a preloaded process and as such should not cause the
253 // MacVim to be opened. We probably got here as a result of the
254 // user quitting MacVim while the process was preloading, so exit
256 // (Don't use mch_exit() since it assumes the process has properly
261 NSBundle *mainBundle = [NSBundle mainBundle];
266 // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
267 // the API to pass Apple Event parameters is broken on 10.4).
268 NSString *path = [mainBundle bundlePath];
269 status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
270 if (noErr == status) {
271 // Pass parameter to the 'Open' Apple Event that tells MacVim not
272 // to open an untitled window.
273 NSAppleEventDescriptor *desc =
274 [NSAppleEventDescriptor recordDescriptor];
275 [desc setParamDescriptor:
276 [NSAppleEventDescriptor descriptorWithBoolean:NO]
277 forKeyword:keyMMUntitledWindow];
279 LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
280 kLSLaunchDefaults, NULL };
281 status = LSOpenFromRefSpec(&spec, NULL);
284 if (noErr != status) {
285 NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
286 path, MMSymlinkWarningString);
290 // Launch MacVim using NSTask. For some reason the above code using
291 // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
292 // fails, the dock icon starts bouncing and never stops). It seems
293 // like rebuilding the Launch Services database takes care of this
294 // problem, but the NSTask way seems more stable so stick with it.
296 // NOTE! Using NSTask to launch the GUI has the negative side-effect
297 // that the GUI won't be activated (or raised) so there is a hack in
298 // MMAppController which raises the app when a new window is opened.
299 NSMutableArray *args = [NSMutableArray arrayWithObjects:
300 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
301 NSString *exeName = [[mainBundle infoDictionary]
302 objectForKey:@"CFBundleExecutable"];
303 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
305 NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
306 MMSymlinkWarningString);
310 [NSTask launchedTaskWithLaunchPath:path arguments:args];
313 // HACK! Poll the mach bootstrap server until it returns a valid
314 // connection to detect that MacVim has finished launching. Also set a
315 // time-out date so that we don't get stuck doing this forever.
316 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
317 while (![self connection] &&
318 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
319 [[NSRunLoop currentRunLoop]
320 runMode:NSDefaultRunLoopMode
321 beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
323 // NOTE: [self connection] will set 'connection' as a side-effect.
325 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
332 [[NSNotificationCenter defaultCenter] addObserver:self
333 selector:@selector(connectionDidDie:)
334 name:NSConnectionDidDieNotification object:connection];
336 appProxy = [[connection rootProxy] retain];
337 [appProxy setProtocolForProxy:@protocol(MMAppProtocol)];
339 int pid = [[NSProcessInfo processInfo] processIdentifier];
341 identifier = [appProxy connectBackend:self pid:pid];
345 @catch (NSException *e) {
346 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
352 - (BOOL)openGUIWindow
354 [self queueMessage:OpenWindowMsgID data:nil];
360 int type = ClearAllDrawType;
362 // Any draw commands in queue are effectively obsolete since this clearAll
363 // will negate any effect they have, therefore we may as well clear the
365 [self clearDrawData];
367 [drawData appendBytes:&type length:sizeof(int)];
370 - (void)clearBlockFromRow:(int)row1 column:(int)col1
371 toRow:(int)row2 column:(int)col2
373 int type = ClearBlockDrawType;
375 [drawData appendBytes:&type length:sizeof(int)];
377 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
378 [drawData appendBytes:&row1 length:sizeof(int)];
379 [drawData appendBytes:&col1 length:sizeof(int)];
380 [drawData appendBytes:&row2 length:sizeof(int)];
381 [drawData appendBytes:&col2 length:sizeof(int)];
384 - (void)deleteLinesFromRow:(int)row count:(int)count
385 scrollBottom:(int)bottom left:(int)left right:(int)right
387 int type = DeleteLinesDrawType;
389 [drawData appendBytes:&type length:sizeof(int)];
391 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
392 [drawData appendBytes:&row length:sizeof(int)];
393 [drawData appendBytes:&count length:sizeof(int)];
394 [drawData appendBytes:&bottom length:sizeof(int)];
395 [drawData appendBytes:&left length:sizeof(int)];
396 [drawData appendBytes:&right length:sizeof(int)];
398 if (left == 0 && right == gui.num_cols-1)
399 [self didChangeWholeLine];
402 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
403 cells:(int)cells flags:(int)flags
405 if (len <= 0 || cells <= 0) return;
407 int type = DrawStringDrawType;
409 [drawData appendBytes:&type length:sizeof(int)];
411 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
412 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
413 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
414 [drawData appendBytes:&row length:sizeof(int)];
415 [drawData appendBytes:&col length:sizeof(int)];
416 [drawData appendBytes:&cells length:sizeof(int)];
417 [drawData appendBytes:&flags length:sizeof(int)];
418 [drawData appendBytes:&len length:sizeof(int)];
419 [drawData appendBytes:s length:len];
422 - (void)insertLinesFromRow:(int)row count:(int)count
423 scrollBottom:(int)bottom left:(int)left right:(int)right
425 int type = InsertLinesDrawType;
427 [drawData appendBytes:&type length:sizeof(int)];
429 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
430 [drawData appendBytes:&row length:sizeof(int)];
431 [drawData appendBytes:&count length:sizeof(int)];
432 [drawData appendBytes:&bottom length:sizeof(int)];
433 [drawData appendBytes:&left length:sizeof(int)];
434 [drawData appendBytes:&right length:sizeof(int)];
436 if (left == 0 && right == gui.num_cols-1)
437 [self didChangeWholeLine];
440 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
441 fraction:(int)percent color:(int)color
443 int type = DrawCursorDrawType;
444 unsigned uc = MM_COLOR(color);
446 [drawData appendBytes:&type length:sizeof(int)];
448 [drawData appendBytes:&uc length:sizeof(unsigned)];
449 [drawData appendBytes:&row length:sizeof(int)];
450 [drawData appendBytes:&col length:sizeof(int)];
451 [drawData appendBytes:&shape length:sizeof(int)];
452 [drawData appendBytes:&percent length:sizeof(int)];
455 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
456 numColumns:(int)nc invert:(int)invert
458 int type = DrawInvertedRectDrawType;
459 [drawData appendBytes:&type length:sizeof(int)];
461 [drawData appendBytes:&row length:sizeof(int)];
462 [drawData appendBytes:&col length:sizeof(int)];
463 [drawData appendBytes:&nr length:sizeof(int)];
464 [drawData appendBytes:&nc length:sizeof(int)];
465 [drawData appendBytes:&invert length:sizeof(int)];
470 // Keep running the run-loop until there is no more input to process.
471 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
472 == kCFRunLoopRunHandledSource)
476 - (void)flushQueue:(BOOL)force
478 // NOTE: This variable allows for better control over when the queue is
479 // flushed. It can be set to YES at the beginning of a sequence of calls
480 // that may potentially add items to the queue, and then restored back to
482 if (flushDisabled) return;
484 if ([drawData length] > 0) {
485 // HACK! Detect changes to 'guifontwide'.
486 if (gui.wide_font != oldWideFont) {
487 gui_mch_free_font(oldWideFont);
488 oldWideFont = gui_mch_retain_font(gui.wide_font);
489 [self setFont:oldWideFont wide:YES];
492 int type = SetCursorPosDrawType;
493 [drawData appendBytes:&type length:sizeof(type)];
494 [drawData appendBytes:&gui.row length:sizeof(gui.row)];
495 [drawData appendBytes:&gui.col length:sizeof(gui.col)];
497 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
498 [self clearDrawData];
501 if ([outputQueue count] > 0) {
502 [self insertVimStateMessage];
505 //NSLog(@"[%s] Flushing (count=%d)", _cmd, [outputQueue count]);
506 [appProxy processInput:outputQueue forIdentifier:identifier];
508 @catch (NSException *e) {
509 NSLog(@"[%s] Exception caught: \"%@\"", _cmd, e);
510 NSLog(@"outputQueue(len:%d)=%@", [outputQueue count]/2,
512 if (![connection isValid]) {
513 NSLog(@"WARNING! Connection is invalid, exit now!");
514 NSLog(@"waitForAck=%d got_int=%d", waitForAck, got_int);
519 [outputQueue removeAllObjects];
523 - (BOOL)waitForInput:(int)milliseconds
525 // Return NO if we timed out waiting for input, otherwise return YES.
526 BOOL inputReceived = NO;
528 // Only start the run loop if the input queue is empty, otherwise process
529 // the input first so that the input on queue isn't delayed.
530 if ([inputQueue count]) {
533 // Wait for the specified amount of time, unless 'milliseconds' is
534 // negative in which case we wait "forever" (1e6 seconds translates to
535 // approximately 11 days).
536 CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
538 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
539 == kCFRunLoopRunHandledSource) {
540 // In order to ensure that all input on the run-loop has been
541 // processed we set the timeout to 0 and keep processing until the
542 // run-loop times out.
548 // The above calls may have placed messages on the input queue so process
549 // it now. This call may enter a blocking loop.
550 if ([inputQueue count] > 0)
551 [self processInputQueue];
553 return inputReceived;
558 // NOTE: This is called if mch_exit() is called. Since we assume here that
559 // the process has started properly, be sure to use exit() instead of
560 // mch_exit() to prematurely terminate a process (or set 'isTerminating'
563 // Make sure no connectionDidDie: notification is received now that we are
565 [[NSNotificationCenter defaultCenter] removeObserver:self];
567 // The 'isTerminating' flag indicates that the frontend is also exiting so
568 // there is no need to flush any more output since the frontend won't look
570 if (!isTerminating && [connection isValid]) {
572 // Flush the entire queue in case a VimLeave autocommand added
573 // something to the queue.
574 [self queueMessage:CloseWindowMsgID data:nil];
575 [appProxy processInput:outputQueue forIdentifier:identifier];
577 @catch (NSException *e) {
578 NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
581 // NOTE: If Cmd-w was pressed to close the window the menu is briefly
582 // highlighted and during this pause the frontend won't receive any DO
583 // messages. If the Vim process exits before this highlighting has
584 // finished Cocoa will emit the following error message:
585 // *** -[NSMachPort handlePortMessage:]: dropping incoming DO message
586 // because the connection or ports are invalid
587 // To avoid this warning we delay here. If the warning still appears
588 // this delay may need to be increased.
592 #ifdef MAC_CLIENTSERVER
593 // The default connection is used for the client/server code.
594 [[NSConnection defaultConnection] setRootObject:nil];
595 [[NSConnection defaultConnection] invalidate];
599 - (void)selectTab:(int)index
601 //NSLog(@"%s%d", _cmd, index);
604 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
605 [self queueMessage:SelectTabMsgID data:data];
610 //NSLog(@"%s", _cmd);
612 NSMutableData *data = [NSMutableData data];
614 int idx = tabpage_index(curtab) - 1;
615 [data appendBytes:&idx length:sizeof(int)];
618 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
619 // Count the number of windows in the tabpage.
620 //win_T *wp = tp->tp_firstwin;
622 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
623 //[data appendBytes:&wincount length:sizeof(int)];
625 int tabProp = MMTabInfoCount;
626 [data appendBytes:&tabProp length:sizeof(int)];
627 for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
628 // This function puts the label of the tab in the global 'NameBuff'.
629 get_tabline_label(tp, (tabProp == MMTabToolTip));
630 NSString *s = [NSString stringWithVimString:NameBuff];
631 int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
635 [data appendBytes:&len length:sizeof(int)];
637 [data appendBytes:[s UTF8String] length:len];
641 [self queueMessage:UpdateTabBarMsgID data:data];
644 - (BOOL)tabBarVisible
646 return tabBarVisible;
649 - (void)showTabBar:(BOOL)enable
651 tabBarVisible = enable;
653 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
654 [self queueMessage:msgid data:nil];
657 - (void)setRows:(int)rows columns:(int)cols
659 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
661 int dim[] = { rows, cols };
662 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
664 [self queueMessage:SetTextDimensionsMsgID data:data];
667 - (void)setWindowTitle:(char *)title
669 NSMutableData *data = [NSMutableData data];
670 int len = strlen(title);
671 if (len <= 0) return;
673 [data appendBytes:&len length:sizeof(int)];
674 [data appendBytes:title length:len];
676 [self queueMessage:SetWindowTitleMsgID data:data];
679 - (void)setDocumentFilename:(char *)filename
681 NSMutableData *data = [NSMutableData data];
682 int len = filename ? strlen(filename) : 0;
684 [data appendBytes:&len length:sizeof(int)];
686 [data appendBytes:filename length:len];
688 [self queueMessage:SetDocumentFilenameMsgID data:data];
691 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
695 [self queueMessage:BrowseForFileMsgID properties:attr];
696 [self flushQueue:YES];
699 [self waitForDialogReturn];
701 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
702 s = [dialogReturn vimStringSave];
704 [dialogReturn release]; dialogReturn = nil;
706 @catch (NSException *e) {
707 NSLog(@"[%s] Exception caught: \"%@\"", _cmd, e);
713 - (oneway void)setDialogReturn:(in bycopy id)obj
715 // NOTE: This is called by
716 // - [MMVimController panelDidEnd:::], and
717 // - [MMVimController alertDidEnd:::],
718 // to indicate that a save/open panel or alert has finished.
720 // We want to distinguish between "no dialog return yet" and "dialog
721 // returned nothing". The former can be tested with dialogReturn == nil,
722 // the latter with dialogReturn == [NSNull null].
723 if (!obj) obj = [NSNull null];
725 if (obj != dialogReturn) {
726 [dialogReturn release];
727 dialogReturn = [obj retain];
731 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
735 [self queueMessage:ShowDialogMsgID properties:attr];
736 [self flushQueue:YES];
739 [self waitForDialogReturn];
741 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
742 && [dialogReturn count]) {
743 retval = [[dialogReturn objectAtIndex:0] intValue];
744 if (txtfield && [dialogReturn count] > 1) {
745 NSString *retString = [dialogReturn objectAtIndex:1];
746 char_u *ret = (char_u*)[retString UTF8String];
748 ret = CONVERT_FROM_UTF8(ret);
750 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
752 CONVERT_FROM_UTF8_FREE(ret);
757 [dialogReturn release]; dialogReturn = nil;
759 @catch (NSException *e) {
760 NSLog(@"[%s] Exception caught: \"%@\"", _cmd, e);
766 - (void)showToolbar:(int)enable flags:(int)flags
768 NSMutableData *data = [NSMutableData data];
770 [data appendBytes:&enable length:sizeof(int)];
771 [data appendBytes:&flags length:sizeof(int)];
773 [self queueMessage:ShowToolbarMsgID data:data];
776 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
778 NSMutableData *data = [NSMutableData data];
780 [data appendBytes:&ident length:sizeof(long)];
781 [data appendBytes:&type length:sizeof(int)];
783 [self queueMessage:CreateScrollbarMsgID data:data];
786 - (void)destroyScrollbarWithIdentifier:(long)ident
788 NSMutableData *data = [NSMutableData data];
789 [data appendBytes:&ident length:sizeof(long)];
791 [self queueMessage:DestroyScrollbarMsgID data:data];
794 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
796 NSMutableData *data = [NSMutableData data];
798 [data appendBytes:&ident length:sizeof(long)];
799 [data appendBytes:&visible length:sizeof(int)];
801 [self queueMessage:ShowScrollbarMsgID data:data];
804 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
806 NSMutableData *data = [NSMutableData data];
808 [data appendBytes:&ident length:sizeof(long)];
809 [data appendBytes:&pos length:sizeof(int)];
810 [data appendBytes:&len length:sizeof(int)];
812 [self queueMessage:SetScrollbarPositionMsgID data:data];
815 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
816 identifier:(long)ident
818 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
819 float prop = (float)size/(max+1);
820 if (fval < 0) fval = 0;
821 else if (fval > 1.0f) fval = 1.0f;
822 if (prop < 0) prop = 0;
823 else if (prop > 1.0f) prop = 1.0f;
825 NSMutableData *data = [NSMutableData data];
827 [data appendBytes:&ident length:sizeof(long)];
828 [data appendBytes:&fval length:sizeof(float)];
829 [data appendBytes:&prop length:sizeof(float)];
831 [self queueMessage:SetScrollbarThumbMsgID data:data];
834 - (void)setFont:(GuiFont)font wide:(BOOL)wide
836 NSString *fontName = (NSString *)font;
838 NSArray *components = [fontName componentsSeparatedByString:@":"];
839 if ([components count] == 2) {
840 size = [[components lastObject] floatValue];
841 fontName = [components objectAtIndex:0];
844 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
845 NSMutableData *data = [NSMutableData data];
846 [data appendBytes:&size length:sizeof(float)];
847 [data appendBytes:&len length:sizeof(int)];
850 [data appendBytes:[fontName UTF8String] length:len];
852 return; // Only the wide font can be set to nothing
854 [self queueMessage:(wide ? SetWideFontMsgID : SetFontMsgID) data:data];
857 - (void)executeActionWithName:(NSString *)name
859 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
862 NSMutableData *data = [NSMutableData data];
864 [data appendBytes:&len length:sizeof(int)];
865 [data appendBytes:[name UTF8String] length:len];
867 [self queueMessage:ExecuteActionMsgID data:data];
871 - (void)setMouseShape:(int)shape
873 NSMutableData *data = [NSMutableData data];
874 [data appendBytes:&shape length:sizeof(int)];
875 [self queueMessage:SetMouseShapeMsgID data:data];
878 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
880 // Vim specifies times in milliseconds, whereas Cocoa wants them in
882 blinkWaitInterval = .001f*wait;
883 blinkOnInterval = .001f*on;
884 blinkOffInterval = .001f*off;
890 [blinkTimer invalidate];
891 [blinkTimer release];
895 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
897 blinkState = MMBlinkStateOn;
899 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
901 selector:@selector(blinkTimerFired:)
902 userInfo:nil repeats:NO] retain];
903 gui_update_cursor(TRUE, FALSE);
904 [self flushQueue:YES];
910 if (MMBlinkStateOff == blinkState) {
911 gui_update_cursor(TRUE, FALSE);
912 [self flushQueue:YES];
915 blinkState = MMBlinkStateNone;
918 - (void)adjustLinespace:(int)linespace
920 NSMutableData *data = [NSMutableData data];
921 [data appendBytes:&linespace length:sizeof(int)];
922 [self queueMessage:AdjustLinespaceMsgID data:data];
927 [self queueMessage:ActivateMsgID data:nil];
930 - (void)setPreEditRow:(int)row column:(int)col
932 NSMutableData *data = [NSMutableData data];
933 [data appendBytes:&row length:sizeof(int)];
934 [data appendBytes:&col length:sizeof(int)];
935 [self queueMessage:SetPreEditPositionMsgID data:data];
938 - (int)lookupColorWithKey:(NSString *)key
940 if (!(key && [key length] > 0))
943 NSString *stripKey = [[[[key lowercaseString]
944 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
945 componentsSeparatedByString:@" "]
946 componentsJoinedByString:@""];
948 if (stripKey && [stripKey length] > 0) {
949 // First of all try to lookup key in the color dictionary; note that
950 // all keys in this dictionary are lowercase with no whitespace.
951 id obj = [colorDict objectForKey:stripKey];
952 if (obj) return [obj intValue];
954 // The key was not in the dictionary; is it perhaps of the form
956 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
957 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
958 [scanner setScanLocation:1];
960 if ([scanner scanHexInt:&hex]) {
965 // As a last resort, check if it is one of the system defined colors.
966 // The keys in this dictionary are also lowercase with no whitespace.
967 obj = [sysColorDict objectForKey:stripKey];
969 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
972 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
973 [col getRed:&r green:&g blue:&b alpha:&a];
974 return (((int)(r*255+.5f) & 0xff) << 16)
975 + (((int)(g*255+.5f) & 0xff) << 8)
976 + ((int)(b*255+.5f) & 0xff);
981 //NSLog(@"WARNING: No color with key %@ found.", stripKey);
985 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
987 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
990 while ((obj = [e nextObject])) {
991 if ([value isEqual:obj])
998 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1000 NSMutableData *data = [NSMutableData data];
1001 [data appendBytes:&fuoptions length:sizeof(int)];
1003 [data appendBytes:&bg length:sizeof(int)];
1004 [self queueMessage:EnterFullscreenMsgID data:data];
1007 - (void)leaveFullscreen
1009 [self queueMessage:LeaveFullscreenMsgID data:nil];
1012 - (void)setFullscreenBackgroundColor:(int)color
1014 NSMutableData *data = [NSMutableData data];
1015 color = MM_COLOR(color);
1016 [data appendBytes:&color length:sizeof(int)];
1018 [self queueMessage:SetFullscreenColorMsgID data:data];
1021 - (void)setAntialias:(BOOL)antialias
1023 int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1025 [self queueMessage:msgid data:nil];
1028 - (void)updateModifiedFlag
1030 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1032 int msgid = [self checkForModifiedBuffers]
1033 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1035 [self queueMessage:msgid data:nil];
1038 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1040 // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1041 // queue is processed since that only happens in waitForInput: (and Vim
1042 // regularly checks for Ctrl-C in between waiting for input).
1043 // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1044 // which waits on the run loop will fail to detect this message (e.g. in
1045 // waitForConnectionAcknowledgement).
1047 if (InsertTextMsgID == msgid && data != nil) {
1048 const void *bytes = [data bytes];
1049 bytes += sizeof(int);
1050 int len = *((int*)bytes); bytes += sizeof(int);
1052 char_u *str = (char_u*)bytes;
1053 if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1054 (str[0] == intr_char && intr_char != Ctrl_C)) {
1056 [inputQueue removeAllObjects];
1060 } else if (TerminateNowMsgID == msgid) {
1061 // Terminate immediately (the frontend is about to quit or this process
1062 // was aborted). Don't preserve modified files since the user would
1063 // already have been presented with a dialog warning if there were any
1064 // modified files when we get here.
1065 isTerminating = YES;
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 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1110 errorString:(out bycopy NSString **)errstr
1112 return evalExprCocoa(expr, errstr);
1116 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1118 NSString *eval = nil;
1119 char_u *s = (char_u*)[expr UTF8String];
1122 s = CONVERT_FROM_UTF8(s);
1125 char_u *res = eval_client_expr_to_string(s);
1128 CONVERT_FROM_UTF8_FREE(s);
1134 s = CONVERT_TO_UTF8(s);
1136 eval = [NSString stringWithUTF8String:(char*)s];
1138 CONVERT_TO_UTF8_FREE(s);
1146 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1148 // TODO: This method should share code with clip_mch_request_selection().
1150 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1151 // If there is no pasteboard, return YES to indicate that there is text
1156 clip_copy_selection();
1158 // Get the text to put on the pasteboard.
1159 long_u llen = 0; char_u *str = 0;
1160 int type = clip_convert_selection(&str, &llen, &clip_star);
1164 // TODO: Avoid overflow.
1165 int len = (int)llen;
1167 if (output_conv.vc_type != CONV_NONE) {
1168 char_u *conv_str = string_convert(&output_conv, str, &len);
1176 NSString *string = [[NSString alloc]
1177 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1179 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1180 [pboard declareTypes:types owner:nil];
1181 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1192 - (oneway void)addReply:(in bycopy NSString *)reply
1193 server:(in byref id <MMVimServerProtocol>)server
1195 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1197 // Replies might come at any time and in any order so we keep them in an
1198 // array inside a dictionary with the send port used as key.
1200 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1201 // HACK! Assume connection uses mach ports.
1202 int port = [(NSMachPort*)[conn sendPort] machPort];
1203 NSNumber *key = [NSNumber numberWithInt:port];
1205 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1207 replies = [NSMutableArray array];
1208 [serverReplyDict setObject:replies forKey:key];
1211 [replies addObject:reply];
1214 - (void)addInput:(in bycopy NSString *)input
1215 client:(in byref id <MMVimClientProtocol>)client
1217 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1219 // NOTE: We don't call addInput: here because it differs from
1220 // server_to_input_buf() in that it always sets the 'silent' flag and we
1221 // don't want the MacVim client/server code to behave differently from
1223 char_u *s = [input vimStringSave];
1224 server_to_input_buf(s);
1227 [self addClient:(id)client];
1230 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1231 client:(in byref id <MMVimClientProtocol>)client
1233 [self addClient:(id)client];
1234 return [self evaluateExpression:expr];
1237 - (void)registerServerWithName:(NSString *)name
1239 NSString *svrName = name;
1240 NSConnection *svrConn = [NSConnection defaultConnection];
1243 for (i = 0; i < MMServerMax; ++i) {
1244 NSString *connName = [self connectionNameFromServerName:svrName];
1246 if ([svrConn registerName:connName]) {
1247 //NSLog(@"Registered server with name: %@", svrName);
1249 // TODO: Set request/reply time-outs to something else?
1251 // Don't wait for requests (time-out means that the message is
1253 [svrConn setRequestTimeout:0];
1254 //[svrConn setReplyTimeout:MMReplyTimeout];
1255 [svrConn setRootObject:self];
1257 // NOTE: 'serverName' is a global variable
1258 serverName = [svrName vimStringSave];
1260 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1263 need_maketitle = TRUE;
1265 [self queueMessage:SetServerNameMsgID data:
1266 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1270 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1274 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1275 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1278 // NOTE: If 'name' equals 'serverName' then the request is local (client
1279 // and server are the same). This case is not handled separately, so a
1280 // connection will be set up anyway (this simplifies the code).
1282 NSConnection *conn = [self connectionForServerName:name];
1285 char_u *s = (char_u*)[name UTF8String];
1287 s = CONVERT_FROM_UTF8(s);
1289 EMSG2(_(e_noserver), s);
1291 CONVERT_FROM_UTF8_FREE(s);
1298 // HACK! Assume connection uses mach ports.
1299 *port = [(NSMachPort*)[conn sendPort] machPort];
1302 id proxy = [conn rootProxy];
1303 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1307 NSString *eval = [proxy evaluateExpression:string client:self];
1310 *reply = [eval vimStringSave];
1312 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1319 [proxy addInput:string client:self];
1322 @catch (NSException *e) {
1323 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1330 - (NSArray *)serverList
1332 NSArray *list = nil;
1334 if ([self connection]) {
1335 id proxy = [connection rootProxy];
1336 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1339 list = [proxy serverList];
1341 @catch (NSException *e) {
1342 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1345 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1351 - (NSString *)peekForReplyOnPort:(int)port
1353 //NSLog(@"%s%d", _cmd, port);
1355 NSNumber *key = [NSNumber numberWithInt:port];
1356 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1357 if (replies && [replies count]) {
1358 //NSLog(@" %d replies, topmost is: %@", [replies count],
1359 // [replies objectAtIndex:0]);
1360 return [replies objectAtIndex:0];
1363 //NSLog(@" No replies");
1367 - (NSString *)waitForReplyOnPort:(int)port
1369 //NSLog(@"%s%d", _cmd, port);
1371 NSConnection *conn = [self connectionForServerPort:port];
1375 NSNumber *key = [NSNumber numberWithInt:port];
1376 NSMutableArray *replies = nil;
1377 NSString *reply = nil;
1379 // Wait for reply as long as the connection to the server is valid (unless
1380 // user interrupts wait with Ctrl-C).
1381 while (!got_int && [conn isValid] &&
1382 !(replies = [serverReplyDict objectForKey:key])) {
1383 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1384 beforeDate:[NSDate distantFuture]];
1388 if ([replies count] > 0) {
1389 reply = [[replies objectAtIndex:0] retain];
1390 //NSLog(@" Got reply: %@", reply);
1391 [replies removeObjectAtIndex:0];
1392 [reply autorelease];
1395 if ([replies count] == 0)
1396 [serverReplyDict removeObjectForKey:key];
1402 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1404 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1407 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1408 [client addReply:reply server:self];
1411 @catch (NSException *e) {
1412 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1415 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1426 - (void)setWaitForAck:(BOOL)yn
1431 - (void)waitForConnectionAcknowledgement
1433 if (!waitForAck) return;
1435 while (waitForAck && !got_int && [connection isValid]) {
1436 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1437 beforeDate:[NSDate distantFuture]];
1438 //NSLog(@" waitForAck=%d got_int=%d isValid=%d",
1439 // waitForAck, got_int, [connection isValid]);
1443 // Never received a connection acknowledgement, so die.
1444 [[NSNotificationCenter defaultCenter] removeObserver:self];
1445 [appProxy release]; appProxy = nil;
1447 // NOTE: We intentionally do not call mch_exit() since this in turn
1448 // will lead to -[MMBackend exit] getting called which we want to
1453 [self processInputQueue];
1456 - (oneway void)acknowledgeConnection
1458 //NSLog(@"%s", _cmd);
1466 @implementation MMBackend (Private)
1468 - (void)clearDrawData
1470 [drawData setLength:0];
1471 numWholeLineChanges = offsetForDrawDataPrune = 0;
1474 - (void)didChangeWholeLine
1476 // It may happen that draw queue is filled up with lots of changes that
1477 // affect a whole row. If the number of such changes equals twice the
1478 // number of visible rows then we can prune some commands off the queue.
1480 // NOTE: If we don't perform this pruning the draw queue may grow
1481 // indefinitely if Vim were to repeatedly send draw commands without ever
1482 // waiting for new input (that's when the draw queue is flushed). The one
1483 // instance I know where this can happen is when a command is executed in
1484 // the shell (think ":grep" with thousands of matches).
1486 ++numWholeLineChanges;
1487 if (numWholeLineChanges == gui.num_rows) {
1488 // Remember the offset to prune up to.
1489 offsetForDrawDataPrune = [drawData length];
1490 } else if (numWholeLineChanges == 2*gui.num_rows) {
1491 // Delete all the unnecessary draw commands.
1492 NSMutableData *d = [[NSMutableData alloc]
1493 initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1494 length:[drawData length] - offsetForDrawDataPrune];
1495 offsetForDrawDataPrune = [d length];
1496 numWholeLineChanges -= gui.num_rows;
1502 - (void)waitForDialogReturn
1504 // Keep processing the run loop until a dialog returns. To avoid getting
1505 // stuck in an endless loop (could happen if the setDialogReturn: message
1506 // was lost) we also do some paranoia checks.
1508 // Note that in Cocoa the user can still resize windows and select menu
1509 // items while a sheet is being displayed, so we can't just wait for the
1510 // first message to arrive and assume that is the setDialogReturn: call.
1512 while (nil == dialogReturn && !got_int && [connection isValid])
1513 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1514 beforeDate:[NSDate distantFuture]];
1516 // Search for any resize messages on the input queue. All other messages
1517 // on the input queue are dropped. The reason why we single out resize
1518 // messages is because the user may have resized the window while a sheet
1520 int i, count = [inputQueue count];
1522 id textDimData = nil;
1524 for (i = count-2; i >= 0; i -= 2) {
1525 int msgid = [[inputQueue objectAtIndex:i] intValue];
1526 if (SetTextDimensionsMsgID == msgid) {
1527 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1533 [inputQueue removeAllObjects];
1536 [inputQueue addObject:
1537 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1538 [inputQueue addObject:textDimData];
1539 [textDimData release];
1544 - (void)insertVimStateMessage
1546 // NOTE: This is the place to add Vim state that needs to be accessed from
1547 // MacVim. Do not add state that could potentially require lots of memory
1548 // since this message gets sent each time the output queue is forcibly
1549 // flushed (e.g. storing the currently selected text would be a bad idea).
1550 // We take this approach of "pushing" the state to MacVim to avoid having
1551 // to make synchronous calls from MacVim to Vim in order to get state.
1553 BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1554 int numTabs = tabpage_index(NULL) - 1;
1558 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1559 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1560 [NSNumber numberWithInt:p_mh], @"p_mh",
1561 [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1562 [NSNumber numberWithBool:mmta], @"p_mmta",
1563 [NSNumber numberWithInt:numTabs], @"numTabs",
1566 // Put the state before all other messages.
1567 int msgid = SetVimStateMsgID;
1568 [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1569 [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1573 - (void)processInputQueue
1575 if ([inputQueue count] == 0) return;
1577 // NOTE: One of the input events may cause this method to be called
1578 // recursively, so copy the input queue to a local variable and clear the
1579 // queue before starting to process input events (otherwise we could get
1580 // stuck in an endless loop).
1581 NSArray *q = [inputQueue copy];
1582 unsigned i, count = [q count];
1584 [inputQueue removeAllObjects];
1586 for (i = 1; i < count; i+=2) {
1587 int msgid = [[q objectAtIndex:i-1] intValue];
1588 id data = [q objectAtIndex:i];
1589 if ([data isEqual:[NSNull null]])
1592 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1593 [self handleInputEvent:msgid data:data];
1597 //NSLog(@"Clear input event queue");
1600 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1602 if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1603 CmdKeyMsgID == msgid) {
1605 const void *bytes = [data bytes];
1606 int mods = *((int*)bytes); bytes += sizeof(int);
1607 int len = *((int*)bytes); bytes += sizeof(int);
1608 NSString *key = [[NSString alloc] initWithBytes:bytes
1610 encoding:NSUTF8StringEncoding];
1611 mods = eventModifierFlagsToVimModMask(mods);
1613 if (InsertTextMsgID == msgid)
1614 [self handleInsertText:key];
1616 [self handleKeyDown:key modifiers:mods];
1619 } else if (ScrollWheelMsgID == msgid) {
1621 const void *bytes = [data bytes];
1623 int row = *((int*)bytes); bytes += sizeof(int);
1624 int col = *((int*)bytes); bytes += sizeof(int);
1625 int flags = *((int*)bytes); bytes += sizeof(int);
1626 float dy = *((float*)bytes); bytes += sizeof(float);
1628 int button = MOUSE_5;
1629 if (dy > 0) button = MOUSE_4;
1631 flags = eventModifierFlagsToVimMouseModMask(flags);
1633 int numLines = (int)round(dy);
1634 if (numLines < 0) numLines = -numLines;
1635 if (numLines == 0) numLines = 1;
1637 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1638 gui.scroll_wheel_force = numLines;
1641 gui_send_mouse_event(button, col, row, NO, flags);
1642 } else if (MouseDownMsgID == msgid) {
1644 const void *bytes = [data bytes];
1646 int row = *((int*)bytes); bytes += sizeof(int);
1647 int col = *((int*)bytes); bytes += sizeof(int);
1648 int button = *((int*)bytes); bytes += sizeof(int);
1649 int flags = *((int*)bytes); bytes += sizeof(int);
1650 int count = *((int*)bytes); bytes += sizeof(int);
1652 button = eventButtonNumberToVimMouseButton(button);
1654 flags = eventModifierFlagsToVimMouseModMask(flags);
1655 gui_send_mouse_event(button, col, row, count>1, flags);
1657 } else if (MouseUpMsgID == msgid) {
1659 const void *bytes = [data bytes];
1661 int row = *((int*)bytes); bytes += sizeof(int);
1662 int col = *((int*)bytes); bytes += sizeof(int);
1663 int flags = *((int*)bytes); bytes += sizeof(int);
1665 flags = eventModifierFlagsToVimMouseModMask(flags);
1667 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1668 } else if (MouseDraggedMsgID == msgid) {
1670 const void *bytes = [data bytes];
1672 int row = *((int*)bytes); bytes += sizeof(int);
1673 int col = *((int*)bytes); bytes += sizeof(int);
1674 int flags = *((int*)bytes); bytes += sizeof(int);
1676 flags = eventModifierFlagsToVimMouseModMask(flags);
1678 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1679 } else if (MouseMovedMsgID == msgid) {
1680 const void *bytes = [data bytes];
1681 int row = *((int*)bytes); bytes += sizeof(int);
1682 int col = *((int*)bytes); bytes += sizeof(int);
1684 gui_mouse_moved(col, row);
1685 } else if (AddInputMsgID == msgid) {
1686 NSString *string = [[NSString alloc] initWithData:data
1687 encoding:NSUTF8StringEncoding];
1689 [self addInput:string];
1692 } else if (SelectTabMsgID == msgid) {
1694 const void *bytes = [data bytes];
1695 int idx = *((int*)bytes) + 1;
1696 //NSLog(@"Selecting tab %d", idx);
1697 send_tabline_event(idx);
1698 } else if (CloseTabMsgID == msgid) {
1700 const void *bytes = [data bytes];
1701 int idx = *((int*)bytes) + 1;
1702 //NSLog(@"Closing tab %d", idx);
1703 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1704 } else if (AddNewTabMsgID == msgid) {
1705 //NSLog(@"Adding new tab");
1706 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1707 } else if (DraggedTabMsgID == msgid) {
1709 const void *bytes = [data bytes];
1710 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1712 int idx = *((int*)bytes);
1715 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1716 || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1718 const void *bytes = [data bytes];
1720 if (SetTextColumnsMsgID != msgid) {
1721 rows = *((int*)bytes); bytes += sizeof(int);
1724 if (SetTextRowsMsgID != msgid) {
1725 cols = *((int*)bytes); bytes += sizeof(int);
1729 if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1730 int dim[2] = { rows, cols };
1731 d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1732 msgid = SetTextDimensionsReplyMsgID;
1735 if (SetTextDimensionsMsgID == msgid)
1736 msgid = SetTextDimensionsReplyMsgID;
1738 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1739 // gui_resize_shell(), so we have to manually set the rows and columns
1740 // here since MacVim doesn't change the rows and columns to avoid
1741 // inconsistent states between Vim and MacVim. The message sent back
1742 // indicates that it is a reply to a message that originated in MacVim
1743 // since we need to be able to determine where a message originated.
1744 [self queueMessage:msgid data:d];
1746 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1747 gui_resize_shell(cols, rows);
1748 } else if (ExecuteMenuMsgID == msgid) {
1749 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1751 NSArray *desc = [attrs objectForKey:@"descriptor"];
1752 vimmenu_T *menu = menu_for_descriptor(desc);
1756 } else if (ToggleToolbarMsgID == msgid) {
1757 [self handleToggleToolbar];
1758 } else if (ScrollbarEventMsgID == msgid) {
1759 [self handleScrollbarEvent:data];
1760 } else if (SetFontMsgID == msgid) {
1761 [self handleSetFont:data];
1762 } else if (VimShouldCloseMsgID == msgid) {
1764 } else if (DropFilesMsgID == msgid) {
1765 [self handleDropFiles:data];
1766 } else if (DropStringMsgID == msgid) {
1767 [self handleDropString:data];
1768 } else if (GotFocusMsgID == msgid) {
1770 [self focusChange:YES];
1771 } else if (LostFocusMsgID == msgid) {
1773 [self focusChange:NO];
1774 } else if (SetMouseShapeMsgID == msgid) {
1775 const void *bytes = [data bytes];
1776 int shape = *((int*)bytes); bytes += sizeof(int);
1777 update_mouseshape(shape);
1778 } else if (XcodeModMsgID == msgid) {
1779 [self handleXcodeMod:data];
1780 } else if (OpenWithArgumentsMsgID == msgid) {
1781 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1782 } else if (FindReplaceMsgID == msgid) {
1783 [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1785 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1789 + (NSDictionary *)specialKeys
1791 static NSDictionary *specialKeys = nil;
1794 NSBundle *mainBundle = [NSBundle mainBundle];
1795 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1797 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1803 - (void)handleInsertText:(NSString *)text
1807 char_u *str = (char_u*)[text UTF8String];
1808 int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1811 char_u *conv_str = NULL;
1812 if (input_conv.vc_type != CONV_NONE) {
1813 conv_str = string_convert(&input_conv, str, &len);
1819 for (i = 0; i < len; ++i) {
1820 add_to_input_buf(str+i, 1);
1821 if (CSI == str[i]) {
1822 // NOTE: If the converted string contains the byte CSI, then it
1823 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1825 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1826 add_to_input_buf(extra, 2);
1836 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1838 // TODO: This code is a horrible mess -- clean up!
1841 char_u *chars = (char_u*)[key UTF8String];
1843 char_u *conv_str = NULL;
1845 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1847 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1848 // that new keys can easily be added.
1849 NSString *specialString = [[MMBackend specialKeys]
1851 if (specialString && [specialString length] > 1) {
1852 //NSLog(@"special key: %@", specialString);
1853 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1854 [specialString characterAtIndex:1]);
1856 ikey = simplify_key(ikey, &mods);
1861 special[1] = K_SECOND(ikey);
1862 special[2] = K_THIRD(ikey);
1866 } else if (1 == length && TAB == chars[0]) {
1867 // Tab is a trouble child:
1868 // - <Tab> is added to the input buffer as is
1869 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1870 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1871 // to be converted to utf-8
1872 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1873 // - <C-Tab> is reserved by Mac OS X
1874 // - <D-Tab> is reserved by Mac OS X
1879 if (mods & MOD_MASK_SHIFT) {
1880 mods &= ~MOD_MASK_SHIFT;
1882 special[1] = K_SECOND(K_S_TAB);
1883 special[2] = K_THIRD(K_S_TAB);
1885 } else if (mods & MOD_MASK_ALT) {
1886 int mtab = 0x80 | TAB;
1890 special[0] = (mtab >> 6) + 0xc0;
1891 special[1] = mtab & 0xbf;
1899 mods &= ~MOD_MASK_ALT;
1901 } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1902 // META key is treated separately. This code was taken from gui_w48.c
1903 // and gui_gtk_x11.c.
1905 int ch = simplify_key(chars[0], &mods);
1907 // Remove the SHIFT modifier for keys where it's already included,
1908 // e.g., '(' and '*'
1909 if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1910 mods &= ~MOD_MASK_SHIFT;
1912 // Interpret the ALT key as making the key META, include SHIFT, etc.
1913 ch = extract_modifiers(ch, &mods);
1919 string[len++] = CSI;
1920 string[len++] = KS_MODIFIER;
1921 string[len++] = mods;
1924 if (IS_SPECIAL(ch)) {
1925 string[len++] = CSI;
1926 string[len++] = K_SECOND(ch);
1927 string[len++] = K_THIRD(ch);
1931 // TODO: What if 'enc' is not "utf-8"?
1932 if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1933 string[len++] = ch & 0xbf;
1934 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1935 if (string[len-1] == CSI) {
1936 string[len++] = KS_EXTRA;
1937 string[len++] = (int)KE_CSI;
1943 add_to_input_buf(string, len);
1945 } else if (length > 0) {
1946 unichar c = [key characterAtIndex:0];
1947 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1948 // [key characterAtIndex:0], mods);
1950 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1951 // cleared since they are already added to the key by the AppKit.
1952 // Unfortunately, the only way to deal with when to clear the modifiers
1953 // or not seems to be to have hard-wired rules like this.
1954 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1955 || 0x9 == c || 0xd == c || ESC == c) ) {
1956 mods &= ~MOD_MASK_SHIFT;
1957 mods &= ~MOD_MASK_CTRL;
1958 //NSLog(@"clear shift ctrl");
1962 if (input_conv.vc_type != CONV_NONE) {
1963 conv_str = string_convert(&input_conv, chars, &length);
1970 if (chars && length > 0) {
1972 //NSLog(@"adding mods: %d", mods);
1974 modChars[1] = KS_MODIFIER;
1976 add_to_input_buf(modChars, 3);
1979 //NSLog(@"add to input buf: 0x%x", chars[0]);
1980 // TODO: Check for CSI bytes?
1981 add_to_input_buf(chars, length);
1990 - (void)queueMessage:(int)msgid data:(NSData *)data
1992 //if (msgid != EnableMenuItemMsgID)
1993 // NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1995 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1997 [outputQueue addObject:data];
1999 [outputQueue addObject:[NSData data]];
2002 - (void)connectionDidDie:(NSNotification *)notification
2004 // If the main connection to MacVim is lost this means that either MacVim
2005 // has crashed or this process did not receive its termination message
2006 // properly (e.g. if the TerminateNowMsgID was dropped).
2008 // NOTE: This is not called if a Vim controller invalidates its connection.
2010 NSLog(@"WARNING[%s]: Main connection was lost before process had a chance "
2011 "to terminate; preserving swap files.", _cmd);
2012 getout_preserve_modified(1);
2015 - (void)blinkTimerFired:(NSTimer *)timer
2017 NSTimeInterval timeInterval = 0;
2019 [blinkTimer release];
2022 if (MMBlinkStateOn == blinkState) {
2023 gui_undraw_cursor();
2024 blinkState = MMBlinkStateOff;
2025 timeInterval = blinkOffInterval;
2026 } else if (MMBlinkStateOff == blinkState) {
2027 gui_update_cursor(TRUE, FALSE);
2028 blinkState = MMBlinkStateOn;
2029 timeInterval = blinkOnInterval;
2032 if (timeInterval > 0) {
2034 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2035 selector:@selector(blinkTimerFired:)
2036 userInfo:nil repeats:NO] retain];
2037 [self flushQueue:YES];
2041 - (void)focusChange:(BOOL)on
2043 gui_focus_change(on);
2046 - (void)handleToggleToolbar
2048 // If 'go' contains 'T', then remove it, else add it.
2050 char_u go[sizeof(GO_ALL)+2];
2055 p = vim_strchr(go, GO_TOOLBAR);
2059 char_u *end = go + len;
2065 go[len] = GO_TOOLBAR;
2069 set_option_value((char_u*)"guioptions", 0, go, 0);
2071 [self redrawScreen];
2074 - (void)handleScrollbarEvent:(NSData *)data
2078 const void *bytes = [data bytes];
2079 long ident = *((long*)bytes); bytes += sizeof(long);
2080 int hitPart = *((int*)bytes); bytes += sizeof(int);
2081 float fval = *((float*)bytes); bytes += sizeof(float);
2082 scrollbar_T *sb = gui_find_scrollbar(ident);
2085 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2086 long value = sb_info->value;
2087 long size = sb_info->size;
2088 long max = sb_info->max;
2089 BOOL isStillDragging = NO;
2090 BOOL updateKnob = YES;
2093 case NSScrollerDecrementPage:
2094 value -= (size > 2 ? size - 2 : 1);
2096 case NSScrollerIncrementPage:
2097 value += (size > 2 ? size - 2 : 1);
2099 case NSScrollerDecrementLine:
2102 case NSScrollerIncrementLine:
2105 case NSScrollerKnob:
2106 isStillDragging = YES;
2108 case NSScrollerKnobSlot:
2109 value = (long)(fval * (max - size + 1));
2116 //NSLog(@"value %d -> %d", sb_info->value, value);
2117 gui_drag_scrollbar(sb, value, isStillDragging);
2120 // Dragging the knob or option+clicking automatically updates
2121 // the knob position (on the actual NSScroller), so we only
2122 // need to set the knob position in the other cases.
2124 // Update both the left&right vertical scrollbars.
2125 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2126 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2127 [self setScrollbarThumbValue:value size:size max:max
2128 identifier:identLeft];
2129 [self setScrollbarThumbValue:value size:size max:max
2130 identifier:identRight];
2132 // Update the horizontal scrollbar.
2133 [self setScrollbarThumbValue:value size:size max:max
2140 - (void)handleSetFont:(NSData *)data
2144 const void *bytes = [data bytes];
2145 float pointSize = *((float*)bytes); bytes += sizeof(float);
2146 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2147 bytes += sizeof(unsigned); // len not used
2149 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2150 [name appendString:[NSString stringWithFormat:@":h%d", (int)pointSize]];
2151 char_u *s = (char_u*)[name UTF8String];
2154 s = CONVERT_FROM_UTF8(s);
2157 set_option_value((char_u*)"guifont", 0, s, 0);
2160 CONVERT_FROM_UTF8_FREE(s);
2163 [self redrawScreen];
2166 - (void)handleDropFiles:(NSData *)data
2168 // TODO: Get rid of this method; instead use Vim script directly. At the
2169 // moment I know how to do this to open files in tabs, but I'm not sure how
2170 // to add the filenames to the command line when in command line mode.
2174 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2177 id obj = [args objectForKey:@"forceOpen"];
2178 BOOL forceOpen = YES;
2180 forceOpen = [obj boolValue];
2182 NSArray *filenames = [args objectForKey:@"filenames"];
2183 if (!(filenames && [filenames count] > 0)) return;
2186 if (!forceOpen && (State & CMDLINE)) {
2187 // HACK! If Vim is in command line mode then the files names
2188 // should be added to the command line, instead of opening the
2189 // files in tabs (unless forceOpen is set). This is taken care of by
2190 // gui_handle_drop().
2191 int n = [filenames count];
2192 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2195 for (i = 0; i < n; ++i)
2196 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2198 // NOTE! This function will free 'fnames'.
2199 // HACK! It is assumed that the 'x' and 'y' arguments are
2200 // unused when in command line mode.
2201 gui_handle_drop(0, 0, 0, fnames, n);
2206 [self handleOpenWithArguments:args];
2210 - (void)handleDropString:(NSData *)data
2215 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2216 const void *bytes = [data bytes];
2217 int len = *((int*)bytes); bytes += sizeof(int);
2218 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2220 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2221 NSRange range = { 0, [string length] };
2222 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2223 withString:@"\x0a" options:0
2226 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2227 options:0 range:range];
2230 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2231 char_u *s = (char_u*)[string UTF8String];
2233 if (input_conv.vc_type != CONV_NONE)
2234 s = string_convert(&input_conv, s, &len);
2236 dnd_yank_drag_data(s, len);
2238 if (input_conv.vc_type != CONV_NONE)
2241 add_to_input_buf(dropkey, sizeof(dropkey));
2245 - (void)startOdbEditWithArguments:(NSDictionary *)args
2247 #ifdef FEAT_ODB_EDITOR
2248 id obj = [args objectForKey:@"remoteID"];
2251 OSType serverID = [obj unsignedIntValue];
2252 NSString *remotePath = [args objectForKey:@"remotePath"];
2254 NSAppleEventDescriptor *token = nil;
2255 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2256 obj = [args objectForKey:@"remoteTokenDescType"];
2257 if (tokenData && obj) {
2258 DescType tokenType = [obj unsignedLongValue];
2259 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2263 NSArray *filenames = [args objectForKey:@"filenames"];
2264 unsigned i, numFiles = [filenames count];
2265 for (i = 0; i < numFiles; ++i) {
2266 NSString *filename = [filenames objectAtIndex:i];
2267 char_u *s = [filename vimStringSave];
2268 buf_T *buf = buflist_findname(s);
2272 if (buf->b_odb_token) {
2273 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2274 buf->b_odb_token = NULL;
2277 if (buf->b_odb_fname) {
2278 vim_free(buf->b_odb_fname);
2279 buf->b_odb_fname = NULL;
2282 buf->b_odb_server_id = serverID;
2285 buf->b_odb_token = [token retain];
2287 buf->b_odb_fname = [remotePath vimStringSave];
2289 NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2293 #endif // FEAT_ODB_EDITOR
2296 - (void)handleXcodeMod:(NSData *)data
2299 const void *bytes = [data bytes];
2300 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2301 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2305 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2306 descriptorWithDescriptorType:type
2312 - (void)handleOpenWithArguments:(NSDictionary *)args
2314 // ARGUMENT: DESCRIPTION:
2315 // -------------------------------------------------------------
2316 // filenames list of filenames
2317 // dontOpen don't open files specified in above argument
2318 // layout which layout to use to open files
2319 // selectionRange range of lines to select
2320 // searchText string to search for
2321 // cursorLine line to position the cursor on
2322 // cursorColumn column to position the cursor on
2323 // (only valid when "cursorLine" is set)
2324 // remoteID ODB parameter
2325 // remotePath ODB parameter
2326 // remoteTokenDescType ODB parameter
2327 // remoteTokenData ODB parameter
2329 //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2331 NSArray *filenames = [args objectForKey:@"filenames"];
2332 int i, numFiles = filenames ? [filenames count] : 0;
2333 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2334 int layout = [[args objectForKey:@"layout"] intValue];
2336 // Change to directory of first file to open if this is an "unused" editor
2337 // (but do not do this if editing remotely).
2338 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2339 && (starting || [self unusedEditor]) ) {
2340 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2346 // When Vim is starting we simply add the files to be opened to the
2347 // global arglist and Vim will take care of opening them for us.
2348 if (openFiles && numFiles > 0) {
2349 for (i = 0; i < numFiles; i++) {
2350 NSString *fname = [filenames objectAtIndex:i];
2353 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2354 || (p = [fname vimStringSave]) == NULL)
2355 exit(2); // See comment in -[MMBackend exit]
2357 alist_add(&global_alist, p, 2);
2360 // Vim will take care of arranging the files added to the arglist
2361 // in windows or tabs; all we must do is to specify which layout to
2363 initialWindowLayout = layout;
2366 // When Vim is already open we resort to some trickery to open the
2367 // files with the specified layout.
2369 // TODO: Figure out a better way to handle this?
2370 if (openFiles && numFiles > 0) {
2371 BOOL oneWindowInTab = topframe ? YES
2372 : (topframe->fr_layout == FR_LEAF);
2373 BOOL bufChanged = NO;
2374 BOOL bufHasFilename = NO;
2376 bufChanged = curbufIsChanged();
2377 bufHasFilename = curbuf->b_ffname != NULL;
2380 // Temporarily disable flushing since the following code may
2381 // potentially cause multiple redraws.
2382 flushDisabled = YES;
2384 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2385 if (WIN_TABS == layout && !onlyOneTab) {
2386 // By going to the last tabpage we ensure that the new tabs
2387 // will appear last (if this call is left out, the taborder
2392 // Make sure we're in normal mode first.
2393 [self addInput:@"<C-\\><C-N>"];
2396 // With "split layout" we open a new tab before opening
2397 // multiple files if the current tab has more than one window
2398 // or if there is exactly one window but whose buffer has a
2399 // filename. (The :drop command ensures modified buffers get
2400 // their own window.)
2401 if ((WIN_HOR == layout || WIN_VER == layout) &&
2402 (!oneWindowInTab || bufHasFilename))
2403 [self addInput:@":tabnew<CR>"];
2405 // The files are opened by constructing a ":drop ..." command
2406 // and executing it.
2407 NSMutableString *cmd = (WIN_TABS == layout)
2408 ? [NSMutableString stringWithString:@":tab drop"]
2409 : [NSMutableString stringWithString:@":drop"];
2411 for (i = 0; i < numFiles; ++i) {
2412 NSString *file = [filenames objectAtIndex:i];
2413 file = [file stringByEscapingSpecialFilenameCharacters];
2414 [cmd appendString:@" "];
2415 [cmd appendString:file];
2418 // Temporarily clear 'suffixes' so that the files are opened in
2419 // the same order as they appear in the "filenames" array.
2420 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2422 [self addInput:cmd];
2424 // Split the view into multiple windows if requested.
2425 if (WIN_HOR == layout)
2426 [self addInput:@"|sall"];
2427 else if (WIN_VER == layout)
2428 [self addInput:@"|vert sall"];
2430 // Restore the old value of 'suffixes'.
2431 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2433 // When opening one file we try to reuse the current window,
2434 // but not if its buffer is modified or has a filename.
2435 // However, the 'arglist' layout always opens the file in the
2437 NSString *file = [[filenames lastObject]
2438 stringByEscapingSpecialFilenameCharacters];
2440 if (WIN_HOR == layout) {
2441 if (!(bufHasFilename || bufChanged))
2442 cmd = [NSString stringWithFormat:@":e %@", file];
2444 cmd = [NSString stringWithFormat:@":sp %@", file];
2445 } else if (WIN_VER == layout) {
2446 if (!(bufHasFilename || bufChanged))
2447 cmd = [NSString stringWithFormat:@":e %@", file];
2449 cmd = [NSString stringWithFormat:@":vsp %@", file];
2450 } else if (WIN_TABS == layout) {
2451 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2452 cmd = [NSString stringWithFormat:@":e %@", file];
2454 cmd = [NSString stringWithFormat:@":tabe %@", file];
2456 // (The :drop command will split if there is a modified
2458 cmd = [NSString stringWithFormat:@":drop %@", file];
2461 [self addInput:cmd];
2462 [self addInput:@"<CR>"];
2465 // Force screen redraw (does it have to be this complicated?).
2466 // (This code was taken from the end of gui_handle_drop().)
2467 update_screen(NOT_VALID);
2470 gui_update_cursor(FALSE, FALSE);
2477 if ([args objectForKey:@"remoteID"]) {
2478 // NOTE: We have to delay processing any ODB related arguments since
2479 // the file(s) may not be opened until the input buffer is processed.
2480 [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2485 NSString *lineString = [args objectForKey:@"cursorLine"];
2486 if (lineString && [lineString intValue] > 0) {
2487 NSString *columnString = [args objectForKey:@"cursorColumn"];
2488 if (!(columnString && [columnString intValue] > 0))
2489 columnString = @"1";
2491 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2492 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2493 [self addInput:cmd];
2496 NSString *rangeString = [args objectForKey:@"selectionRange"];
2498 // Build a command line string that will select the given range of
2499 // lines. If range.length == 0, then position the cursor on the given
2500 // line but do not select.
2501 NSRange range = NSRangeFromString(rangeString);
2503 if (range.length > 0) {
2504 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2505 NSMaxRange(range), range.location];
2507 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2511 [self addInput:cmd];
2514 NSString *searchText = [args objectForKey:@"searchText"];
2516 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2521 - (BOOL)checkForModifiedBuffers
2524 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2525 if (bufIsChanged(buf)) {
2533 - (void)addInput:(NSString *)input
2535 // NOTE: This code is essentially identical to server_to_input_buf(),
2536 // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2537 char_u *string = [input vimStringSave];
2538 if (!string) return;
2540 /* Set 'cpoptions' the way we want it.
2541 * B set - backslashes are *not* treated specially
2542 * k set - keycodes are *not* reverse-engineered
2543 * < unset - <Key> sequences *are* interpreted
2544 * The last but one parameter of replace_termcodes() is TRUE so that the
2545 * <lt> sequence is recognised - needed for a real backslash.
2548 char_u *cpo_save = p_cpo;
2549 p_cpo = (char_u *)"Bk";
2550 char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2553 if (*ptr != NUL) /* trailing CTRL-V results in nothing */
2556 * Add the string to the input stream.
2557 * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2559 * First clear typed characters from the typeahead buffer, there could
2560 * be half a mapping there. Then append to the existing string, so
2561 * that multiple commands from a client are concatenated.
2563 if (typebuf.tb_maplen < typebuf.tb_len)
2564 del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2565 (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2567 /* Let input_available() know we inserted text in the typeahead
2569 typebuf_was_filled = TRUE;
2575 - (BOOL)unusedEditor
2577 BOOL oneWindowInTab = topframe ? YES
2578 : (topframe->fr_layout == FR_LEAF);
2579 BOOL bufChanged = NO;
2580 BOOL bufHasFilename = NO;
2582 bufChanged = curbufIsChanged();
2583 bufHasFilename = curbuf->b_ffname != NULL;
2586 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2588 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2591 - (void)redrawScreen
2593 // Force screen redraw (does it have to be this complicated?).
2594 redraw_all_later(CLEAR);
2595 update_screen(NOT_VALID);
2598 gui_update_cursor(FALSE, FALSE);
2600 // HACK! The cursor is not put back at the command line by the above
2601 // "redraw commands". The following test seems to do the trick though.
2602 if (State & CMDLINE)
2606 - (void)handleFindReplace:(NSDictionary *)args
2610 NSString *findString = [args objectForKey:@"find"];
2611 if (!findString) return;
2613 char_u *find = [findString vimStringSave];
2614 char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2615 int flags = [[args objectForKey:@"flags"] intValue];
2617 // NOTE: The flag 0x100 is used to indicate a backward search.
2618 gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2624 @end // MMBackend (Private)
2629 @implementation MMBackend (ClientServer)
2631 - (NSString *)connectionNameFromServerName:(NSString *)name
2633 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2635 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2639 - (NSConnection *)connectionForServerName:(NSString *)name
2641 // TODO: Try 'name%d' if 'name' fails.
2642 NSString *connName = [self connectionNameFromServerName:name];
2643 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2646 svrConn = [NSConnection connectionWithRegisteredName:connName
2648 // Try alternate server...
2649 if (!svrConn && alternateServerName) {
2650 //NSLog(@" trying to connect to alternate server: %@",
2651 // alternateServerName);
2652 connName = [self connectionNameFromServerName:alternateServerName];
2653 svrConn = [NSConnection connectionWithRegisteredName:connName
2657 // Try looking for alternate servers...
2659 //NSLog(@" looking for alternate servers...");
2660 NSString *alt = [self alternateServerNameForName:name];
2661 if (alt != alternateServerName) {
2662 //NSLog(@" found alternate server: %@", string);
2663 [alternateServerName release];
2664 alternateServerName = [alt copy];
2668 // Try alternate server again...
2669 if (!svrConn && alternateServerName) {
2670 //NSLog(@" trying to connect to alternate server: %@",
2671 // alternateServerName);
2672 connName = [self connectionNameFromServerName:alternateServerName];
2673 svrConn = [NSConnection connectionWithRegisteredName:connName
2678 [connectionNameDict setObject:svrConn forKey:connName];
2680 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2681 [[NSNotificationCenter defaultCenter] addObserver:self
2682 selector:@selector(serverConnectionDidDie:)
2683 name:NSConnectionDidDieNotification object:svrConn];
2690 - (NSConnection *)connectionForServerPort:(int)port
2693 NSEnumerator *e = [connectionNameDict objectEnumerator];
2695 while ((conn = [e nextObject])) {
2696 // HACK! Assume connection uses mach ports.
2697 if (port == [(NSMachPort*)[conn sendPort] machPort])
2704 - (void)serverConnectionDidDie:(NSNotification *)notification
2706 //NSLog(@"%s%@", _cmd, notification);
2708 NSConnection *svrConn = [notification object];
2710 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2711 [[NSNotificationCenter defaultCenter]
2713 name:NSConnectionDidDieNotification
2716 [connectionNameDict removeObjectsForKeys:
2717 [connectionNameDict allKeysForObject:svrConn]];
2719 // HACK! Assume connection uses mach ports.
2720 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2721 NSNumber *key = [NSNumber numberWithInt:port];
2723 [clientProxyDict removeObjectForKey:key];
2724 [serverReplyDict removeObjectForKey:key];
2727 - (void)addClient:(NSDistantObject *)client
2729 NSConnection *conn = [client connectionForProxy];
2730 // HACK! Assume connection uses mach ports.
2731 int port = [(NSMachPort*)[conn sendPort] machPort];
2732 NSNumber *key = [NSNumber numberWithInt:port];
2734 if (![clientProxyDict objectForKey:key]) {
2735 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2736 [clientProxyDict setObject:client forKey:key];
2739 // NOTE: 'clientWindow' is a global variable which is used by <client>
2740 clientWindow = port;
2743 - (NSString *)alternateServerNameForName:(NSString *)name
2745 if (!(name && [name length] > 0))
2748 // Only look for alternates if 'name' doesn't end in a digit.
2749 unichar lastChar = [name characterAtIndex:[name length]-1];
2750 if (lastChar >= '0' && lastChar <= '9')
2753 // Look for alternates among all current servers.
2754 NSArray *list = [self serverList];
2755 if (!(list && [list count] > 0))
2758 // Filter out servers starting with 'name' and ending with a number. The
2759 // (?i) pattern ensures that the match is case insensitive.
2760 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2761 NSPredicate *pred = [NSPredicate predicateWithFormat:
2762 @"SELF MATCHES %@", pat];
2763 list = [list filteredArrayUsingPredicate:pred];
2764 if ([list count] > 0) {
2765 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2766 return [list objectAtIndex:0];
2772 @end // MMBackend (ClientServer)
2777 @implementation NSString (MMServerNameCompare)
2778 - (NSComparisonResult)serverNameCompare:(NSString *)string
2780 return [self compare:string
2781 options:NSCaseInsensitiveSearch|NSNumericSearch];
2788 static int eventModifierFlagsToVimModMask(int modifierFlags)
2792 if (modifierFlags & NSShiftKeyMask)
2793 modMask |= MOD_MASK_SHIFT;
2794 if (modifierFlags & NSControlKeyMask)
2795 modMask |= MOD_MASK_CTRL;
2796 if (modifierFlags & NSAlternateKeyMask)
2797 modMask |= MOD_MASK_ALT;
2798 if (modifierFlags & NSCommandKeyMask)
2799 modMask |= MOD_MASK_CMD;
2804 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2808 if (modifierFlags & NSShiftKeyMask)
2809 modMask |= MOUSE_SHIFT;
2810 if (modifierFlags & NSControlKeyMask)
2811 modMask |= MOUSE_CTRL;
2812 if (modifierFlags & NSAlternateKeyMask)
2813 modMask |= MOUSE_ALT;
2818 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2820 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2822 return (buttonNumber >= 0 && buttonNumber < 3)
2823 ? mouseButton[buttonNumber] : -1;
2828 // This function is modeled after the VimToPython function found in if_python.c
2829 // NB This does a deep copy by value, it does not lookup references like the
2830 // VimToPython function does. This is because I didn't want to deal with the
2831 // retain cycles that this would create, and we can cover 99% of the use cases
2832 // by ignoring it. If we ever switch to using GC in MacVim then this
2833 // functionality can be implemented easily.
2834 static id vimToCocoa(typval_T * tv, int depth)
2840 // Avoid infinite recursion
2845 if (tv->v_type == VAR_STRING) {
2846 char_u * val = tv->vval.v_string;
2847 // val can be NULL if the string is empty
2849 result = [NSString string];
2852 val = CONVERT_TO_UTF8(val);
2854 result = [NSString stringWithUTF8String:(char*)val];
2856 CONVERT_TO_UTF8_FREE(val);
2859 } else if (tv->v_type == VAR_NUMBER) {
2860 // looks like sizeof(varnumber_T) is always <= sizeof(long)
2861 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2862 } else if (tv->v_type == VAR_LIST) {
2863 list_T * list = tv->vval.v_list;
2866 NSMutableArray * arr = result = [NSMutableArray array];
2869 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2870 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2871 [arr addObject:newObj];
2874 } else if (tv->v_type == VAR_DICT) {
2875 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2877 if (tv->vval.v_dict != NULL) {
2878 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2879 int todo = ht->ht_used;
2883 for (hi = ht->ht_array; todo > 0; ++hi) {
2884 if (!HASHITEM_EMPTY(hi)) {
2887 di = dict_lookup(hi);
2888 newObj = vimToCocoa(&di->di_tv, depth + 1);
2890 char_u * keyval = hi->hi_key;
2892 keyval = CONVERT_TO_UTF8(keyval);
2894 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2896 CONVERT_TO_UTF8_FREE(keyval);
2898 [dict setObject:newObj forKey:key];
2902 } else { // only func refs should fall into this category?
2910 // This function is modeled after eval_client_expr_to_string found in main.c
2911 // Returns nil if there was an error evaluating the expression, and writes a
2912 // message to errorStr.
2913 // TODO Get the error that occurred while evaluating the expression in vim
2915 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2918 char_u *s = (char_u*)[expr UTF8String];
2921 s = CONVERT_FROM_UTF8(s);
2924 int save_dbl = debug_break_level;
2925 int save_ro = redir_off;
2927 debug_break_level = -1;
2931 typval_T * tvres = eval_expr(s, NULL);
2933 debug_break_level = save_dbl;
2934 redir_off = save_ro;
2941 CONVERT_FROM_UTF8_FREE(s);
2946 gui_update_cursor(FALSE, FALSE);
2949 if (tvres == NULL) {
2951 *errstr = @"Expression evaluation failed.";
2954 id res = vimToCocoa(tvres, 1);
2959 *errstr = @"Conversion to cocoa values failed.";
2967 @implementation NSString (VimStrings)
2969 + (id)stringWithVimString:(char_u *)s
2971 // This method ensures a non-nil string is returned. If 's' cannot be
2972 // converted to a utf-8 string it is assumed to be latin-1. If conversion
2973 // still fails an empty NSString is returned.
2974 NSString *string = nil;
2977 s = CONVERT_TO_UTF8(s);
2979 string = [NSString stringWithUTF8String:(char*)s];
2981 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2983 string = [NSString stringWithCString:(char*)s
2984 encoding:NSISOLatin1StringEncoding];
2987 CONVERT_TO_UTF8_FREE(s);
2991 return string != nil ? string : [NSString string];
2994 - (char_u *)vimStringSave
2996 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2999 s = CONVERT_FROM_UTF8(s);
3001 ret = vim_strsave(s);
3003 CONVERT_FROM_UTF8_FREE(s);
3009 @end // NSString (VimStrings)