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 [appProxy processInput:outputQueue forIdentifier:identifier];
507 @catch (NSException *e) {
508 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
509 NSLog(@"outputQueue(len:%d)=%@", [outputQueue count]/2,
511 if (![connection isValid]) {
512 NSLog(@"WARNING! Connection is invalid, exit now!");
513 NSLog(@"waitForAck=%d got_int=%d", waitForAck, got_int);
518 [outputQueue removeAllObjects];
522 - (BOOL)waitForInput:(int)milliseconds
524 // Return NO if we timed out waiting for input, otherwise return YES.
525 BOOL inputReceived = NO;
527 // Only start the run loop if the input queue is empty, otherwise process
528 // the input first so that the input on queue isn't delayed.
529 if ([inputQueue count]) {
532 // Wait for the specified amount of time, unless 'milliseconds' is
533 // negative in which case we wait "forever" (1e6 seconds translates to
534 // approximately 11 days).
535 CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
537 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
538 == kCFRunLoopRunHandledSource) {
539 // In order to ensure that all input on the run-loop has been
540 // processed we set the timeout to 0 and keep processing until the
541 // run-loop times out.
547 // The above calls may have placed messages on the input queue so process
548 // it now. This call may enter a blocking loop.
549 if ([inputQueue count] > 0)
550 [self processInputQueue];
552 return inputReceived;
557 // NOTE: This is called if mch_exit() is called. Since we assume here that
558 // the process has started properly, be sure to use exit() instead of
559 // mch_exit() to prematurely terminate a process (or set 'isTerminating'
562 // Make sure no connectionDidDie: notification is received now that we are
564 [[NSNotificationCenter defaultCenter] removeObserver:self];
566 // The 'isTerminating' flag indicates that the frontend is also exiting so
567 // there is no need to flush any more output since the frontend won't look
569 if (!isTerminating && [connection isValid]) {
571 // Flush the entire queue in case a VimLeave autocommand added
572 // something to the queue.
573 [self queueMessage:CloseWindowMsgID data:nil];
574 [appProxy processInput:outputQueue forIdentifier:identifier];
576 @catch (NSException *e) {
577 NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
580 // NOTE: If Cmd-w was pressed to close the window the menu is briefly
581 // highlighted and during this pause the frontend won't receive any DO
582 // messages. If the Vim process exits before this highlighting has
583 // finished Cocoa will emit the following error message:
584 // *** -[NSMachPort handlePortMessage:]: dropping incoming DO message
585 // because the connection or ports are invalid
586 // To avoid this warning we delay here. If the warning still appears
587 // this delay may need to be increased.
591 #ifdef MAC_CLIENTSERVER
592 // The default connection is used for the client/server code.
593 [[NSConnection defaultConnection] setRootObject:nil];
594 [[NSConnection defaultConnection] invalidate];
598 - (void)selectTab:(int)index
600 //NSLog(@"%s%d", _cmd, index);
603 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
604 [self queueMessage:SelectTabMsgID data:data];
609 //NSLog(@"%s", _cmd);
611 NSMutableData *data = [NSMutableData data];
613 int idx = tabpage_index(curtab) - 1;
614 [data appendBytes:&idx length:sizeof(int)];
617 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
618 // Count the number of windows in the tabpage.
619 //win_T *wp = tp->tp_firstwin;
621 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
622 //[data appendBytes:&wincount length:sizeof(int)];
624 int tabProp = MMTabInfoCount;
625 [data appendBytes:&tabProp length:sizeof(int)];
626 for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
627 // This function puts the label of the tab in the global 'NameBuff'.
628 get_tabline_label(tp, (tabProp == MMTabToolTip));
629 NSString *s = [NSString stringWithVimString:NameBuff];
630 int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
634 [data appendBytes:&len length:sizeof(int)];
636 [data appendBytes:[s UTF8String] length:len];
640 [self queueMessage:UpdateTabBarMsgID data:data];
643 - (BOOL)tabBarVisible
645 return tabBarVisible;
648 - (void)showTabBar:(BOOL)enable
650 tabBarVisible = enable;
652 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
653 [self queueMessage:msgid data:nil];
656 - (void)setRows:(int)rows columns:(int)cols
658 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
660 int dim[] = { rows, cols };
661 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
663 [self queueMessage:SetTextDimensionsMsgID data:data];
666 - (void)setWindowTitle:(char *)title
668 NSMutableData *data = [NSMutableData data];
669 int len = strlen(title);
670 if (len <= 0) return;
672 [data appendBytes:&len length:sizeof(int)];
673 [data appendBytes:title length:len];
675 [self queueMessage:SetWindowTitleMsgID data:data];
678 - (void)setDocumentFilename:(char *)filename
680 NSMutableData *data = [NSMutableData data];
681 int len = filename ? strlen(filename) : 0;
683 [data appendBytes:&len length:sizeof(int)];
685 [data appendBytes:filename length:len];
687 [self queueMessage:SetDocumentFilenameMsgID data:data];
690 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
694 [self queueMessage:BrowseForFileMsgID properties:attr];
695 [self flushQueue:YES];
698 [self waitForDialogReturn];
700 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
701 s = [dialogReturn vimStringSave];
703 [dialogReturn release]; dialogReturn = nil;
705 @catch (NSException *e) {
706 NSLog(@"[%s] Exception caught: \"%@\"", _cmd, e);
712 - (oneway void)setDialogReturn:(in bycopy id)obj
714 // NOTE: This is called by
715 // - [MMVimController panelDidEnd:::], and
716 // - [MMVimController alertDidEnd:::],
717 // to indicate that a save/open panel or alert has finished.
719 // We want to distinguish between "no dialog return yet" and "dialog
720 // returned nothing". The former can be tested with dialogReturn == nil,
721 // the latter with dialogReturn == [NSNull null].
722 if (!obj) obj = [NSNull null];
724 if (obj != dialogReturn) {
725 [dialogReturn release];
726 dialogReturn = [obj retain];
730 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
734 [self queueMessage:ShowDialogMsgID properties:attr];
735 [self flushQueue:YES];
738 [self waitForDialogReturn];
740 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
741 && [dialogReturn count]) {
742 retval = [[dialogReturn objectAtIndex:0] intValue];
743 if (txtfield && [dialogReturn count] > 1) {
744 NSString *retString = [dialogReturn objectAtIndex:1];
745 char_u *ret = (char_u*)[retString UTF8String];
747 ret = CONVERT_FROM_UTF8(ret);
749 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
751 CONVERT_FROM_UTF8_FREE(ret);
756 [dialogReturn release]; dialogReturn = nil;
758 @catch (NSException *e) {
759 NSLog(@"[%s] Exception caught: \"%@\"", _cmd, e);
765 - (void)showToolbar:(int)enable flags:(int)flags
767 NSMutableData *data = [NSMutableData data];
769 [data appendBytes:&enable length:sizeof(int)];
770 [data appendBytes:&flags length:sizeof(int)];
772 [self queueMessage:ShowToolbarMsgID data:data];
775 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
777 NSMutableData *data = [NSMutableData data];
779 [data appendBytes:&ident length:sizeof(long)];
780 [data appendBytes:&type length:sizeof(int)];
782 [self queueMessage:CreateScrollbarMsgID data:data];
785 - (void)destroyScrollbarWithIdentifier:(long)ident
787 NSMutableData *data = [NSMutableData data];
788 [data appendBytes:&ident length:sizeof(long)];
790 [self queueMessage:DestroyScrollbarMsgID data:data];
793 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
795 NSMutableData *data = [NSMutableData data];
797 [data appendBytes:&ident length:sizeof(long)];
798 [data appendBytes:&visible length:sizeof(int)];
800 [self queueMessage:ShowScrollbarMsgID data:data];
803 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
805 NSMutableData *data = [NSMutableData data];
807 [data appendBytes:&ident length:sizeof(long)];
808 [data appendBytes:&pos length:sizeof(int)];
809 [data appendBytes:&len length:sizeof(int)];
811 [self queueMessage:SetScrollbarPositionMsgID data:data];
814 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
815 identifier:(long)ident
817 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
818 float prop = (float)size/(max+1);
819 if (fval < 0) fval = 0;
820 else if (fval > 1.0f) fval = 1.0f;
821 if (prop < 0) prop = 0;
822 else if (prop > 1.0f) prop = 1.0f;
824 NSMutableData *data = [NSMutableData data];
826 [data appendBytes:&ident length:sizeof(long)];
827 [data appendBytes:&fval length:sizeof(float)];
828 [data appendBytes:&prop length:sizeof(float)];
830 [self queueMessage:SetScrollbarThumbMsgID data:data];
833 - (void)setFont:(GuiFont)font wide:(BOOL)wide
835 NSString *fontName = (NSString *)font;
837 NSArray *components = [fontName componentsSeparatedByString:@":"];
838 if ([components count] == 2) {
839 size = [[components lastObject] floatValue];
840 fontName = [components objectAtIndex:0];
843 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
844 NSMutableData *data = [NSMutableData data];
845 [data appendBytes:&size length:sizeof(float)];
846 [data appendBytes:&len length:sizeof(int)];
849 [data appendBytes:[fontName UTF8String] length:len];
851 return; // Only the wide font can be set to nothing
853 [self queueMessage:(wide ? SetWideFontMsgID : SetFontMsgID) data:data];
856 - (void)executeActionWithName:(NSString *)name
858 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
861 NSMutableData *data = [NSMutableData data];
863 [data appendBytes:&len length:sizeof(int)];
864 [data appendBytes:[name UTF8String] length:len];
866 [self queueMessage:ExecuteActionMsgID data:data];
870 - (void)setMouseShape:(int)shape
872 NSMutableData *data = [NSMutableData data];
873 [data appendBytes:&shape length:sizeof(int)];
874 [self queueMessage:SetMouseShapeMsgID data:data];
877 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
879 // Vim specifies times in milliseconds, whereas Cocoa wants them in
881 blinkWaitInterval = .001f*wait;
882 blinkOnInterval = .001f*on;
883 blinkOffInterval = .001f*off;
889 [blinkTimer invalidate];
890 [blinkTimer release];
894 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
896 blinkState = MMBlinkStateOn;
898 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
900 selector:@selector(blinkTimerFired:)
901 userInfo:nil repeats:NO] retain];
902 gui_update_cursor(TRUE, FALSE);
903 [self flushQueue:YES];
909 if (MMBlinkStateOff == blinkState) {
910 gui_update_cursor(TRUE, FALSE);
911 [self flushQueue:YES];
914 blinkState = MMBlinkStateNone;
917 - (void)adjustLinespace:(int)linespace
919 NSMutableData *data = [NSMutableData data];
920 [data appendBytes:&linespace length:sizeof(int)];
921 [self queueMessage:AdjustLinespaceMsgID data:data];
926 [self queueMessage:ActivateMsgID data:nil];
929 - (void)setPreEditRow:(int)row column:(int)col
931 NSMutableData *data = [NSMutableData data];
932 [data appendBytes:&row length:sizeof(int)];
933 [data appendBytes:&col length:sizeof(int)];
934 [self queueMessage:SetPreEditPositionMsgID data:data];
937 - (int)lookupColorWithKey:(NSString *)key
939 if (!(key && [key length] > 0))
942 NSString *stripKey = [[[[key lowercaseString]
943 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
944 componentsSeparatedByString:@" "]
945 componentsJoinedByString:@""];
947 if (stripKey && [stripKey length] > 0) {
948 // First of all try to lookup key in the color dictionary; note that
949 // all keys in this dictionary are lowercase with no whitespace.
950 id obj = [colorDict objectForKey:stripKey];
951 if (obj) return [obj intValue];
953 // The key was not in the dictionary; is it perhaps of the form
955 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
956 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
957 [scanner setScanLocation:1];
959 if ([scanner scanHexInt:&hex]) {
964 // As a last resort, check if it is one of the system defined colors.
965 // The keys in this dictionary are also lowercase with no whitespace.
966 obj = [sysColorDict objectForKey:stripKey];
968 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
971 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
972 [col getRed:&r green:&g blue:&b alpha:&a];
973 return (((int)(r*255+.5f) & 0xff) << 16)
974 + (((int)(g*255+.5f) & 0xff) << 8)
975 + ((int)(b*255+.5f) & 0xff);
980 //NSLog(@"WARNING: No color with key %@ found.", stripKey);
984 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
986 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
989 while ((obj = [e nextObject])) {
990 if ([value isEqual:obj])
997 - (void)enterFullscreen:(int)fuoptions background:(int)bg
999 NSMutableData *data = [NSMutableData data];
1000 [data appendBytes:&fuoptions length:sizeof(int)];
1002 [data appendBytes:&bg length:sizeof(int)];
1003 [self queueMessage:EnterFullscreenMsgID data:data];
1006 - (void)leaveFullscreen
1008 [self queueMessage:LeaveFullscreenMsgID data:nil];
1011 - (void)setFullscreenBackgroundColor:(int)color
1013 NSMutableData *data = [NSMutableData data];
1014 color = MM_COLOR(color);
1015 [data appendBytes:&color length:sizeof(int)];
1017 [self queueMessage:SetFullscreenColorMsgID data:data];
1020 - (void)setAntialias:(BOOL)antialias
1022 int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1024 [self queueMessage:msgid data:nil];
1027 - (void)updateModifiedFlag
1029 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1031 int msgid = [self checkForModifiedBuffers]
1032 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1034 [self queueMessage:msgid data:nil];
1037 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1039 // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1040 // queue is processed since that only happens in waitForInput: (and Vim
1041 // regularly checks for Ctrl-C in between waiting for input).
1042 // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1043 // which waits on the run loop will fail to detect this message (e.g. in
1044 // waitForConnectionAcknowledgement).
1046 if (InsertTextMsgID == msgid && data != nil) {
1047 const void *bytes = [data bytes];
1048 bytes += sizeof(int);
1049 int len = *((int*)bytes); bytes += sizeof(int);
1051 char_u *str = (char_u*)bytes;
1052 if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1053 (str[0] == intr_char && intr_char != Ctrl_C)) {
1055 [inputQueue removeAllObjects];
1059 } else if (TerminateNowMsgID == msgid) {
1060 // Terminate immediately (the frontend is about to quit or this process
1061 // was aborted). Don't preserve modified files since the user would
1062 // already have been presented with a dialog warning if there were any
1063 // modified files when we get here.
1064 isTerminating = YES;
1069 // Remove all previous instances of this message from the input queue, else
1070 // the input queue may fill up as a result of Vim not being able to keep up
1071 // with the speed at which new messages are received.
1072 // Keyboard input is never dropped, unless the input represents and
1073 // auto-repeated key.
1075 BOOL isKeyRepeat = NO;
1076 BOOL isKeyboardInput = NO;
1078 if (data && (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1079 CmdKeyMsgID == msgid)) {
1080 isKeyboardInput = YES;
1082 // The lowest bit of the first int is set if this key is a repeat.
1083 int flags = *((int*)[data bytes]);
1088 // Keyboard input is not removed from the queue; repeats are ignored if
1089 // there already is keyboard input on the input queue.
1090 if (isKeyRepeat || !isKeyboardInput) {
1091 int i, count = [inputQueue count];
1092 for (i = 1; i < count; i+=2) {
1093 if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1097 [inputQueue removeObjectAtIndex:i];
1098 [inputQueue removeObjectAtIndex:i-1];
1104 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1105 [inputQueue addObject:(data ? (id)data : [NSNull null])];
1108 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1110 // This is just a convenience method that allows the frontend to delay
1111 // sending messages.
1112 int i, count = [messages count];
1113 for (i = 1; i < count; i+=2)
1114 [self processInput:[[messages objectAtIndex:i-1] intValue]
1115 data:[messages objectAtIndex:i]];
1118 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1119 errorString:(out bycopy NSString **)errstr
1121 return evalExprCocoa(expr, errstr);
1125 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1127 NSString *eval = nil;
1128 char_u *s = (char_u*)[expr UTF8String];
1131 s = CONVERT_FROM_UTF8(s);
1134 char_u *res = eval_client_expr_to_string(s);
1137 CONVERT_FROM_UTF8_FREE(s);
1143 s = CONVERT_TO_UTF8(s);
1145 eval = [NSString stringWithUTF8String:(char*)s];
1147 CONVERT_TO_UTF8_FREE(s);
1155 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1157 // TODO: This method should share code with clip_mch_request_selection().
1159 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1160 // If there is no pasteboard, return YES to indicate that there is text
1165 clip_copy_selection();
1167 // Get the text to put on the pasteboard.
1168 long_u llen = 0; char_u *str = 0;
1169 int type = clip_convert_selection(&str, &llen, &clip_star);
1173 // TODO: Avoid overflow.
1174 int len = (int)llen;
1176 if (output_conv.vc_type != CONV_NONE) {
1177 char_u *conv_str = string_convert(&output_conv, str, &len);
1185 NSString *string = [[NSString alloc]
1186 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1188 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1189 [pboard declareTypes:types owner:nil];
1190 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1201 - (oneway void)addReply:(in bycopy NSString *)reply
1202 server:(in byref id <MMVimServerProtocol>)server
1204 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1206 // Replies might come at any time and in any order so we keep them in an
1207 // array inside a dictionary with the send port used as key.
1209 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1210 // HACK! Assume connection uses mach ports.
1211 int port = [(NSMachPort*)[conn sendPort] machPort];
1212 NSNumber *key = [NSNumber numberWithInt:port];
1214 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1216 replies = [NSMutableArray array];
1217 [serverReplyDict setObject:replies forKey:key];
1220 [replies addObject:reply];
1223 - (void)addInput:(in bycopy NSString *)input
1224 client:(in byref id <MMVimClientProtocol>)client
1226 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1228 // NOTE: We don't call addInput: here because it differs from
1229 // server_to_input_buf() in that it always sets the 'silent' flag and we
1230 // don't want the MacVim client/server code to behave differently from
1232 char_u *s = [input vimStringSave];
1233 server_to_input_buf(s);
1236 [self addClient:(id)client];
1239 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1240 client:(in byref id <MMVimClientProtocol>)client
1242 [self addClient:(id)client];
1243 return [self evaluateExpression:expr];
1246 - (void)registerServerWithName:(NSString *)name
1248 NSString *svrName = name;
1249 NSConnection *svrConn = [NSConnection defaultConnection];
1252 for (i = 0; i < MMServerMax; ++i) {
1253 NSString *connName = [self connectionNameFromServerName:svrName];
1255 if ([svrConn registerName:connName]) {
1256 //NSLog(@"Registered server with name: %@", svrName);
1258 // TODO: Set request/reply time-outs to something else?
1260 // Don't wait for requests (time-out means that the message is
1262 [svrConn setRequestTimeout:0];
1263 //[svrConn setReplyTimeout:MMReplyTimeout];
1264 [svrConn setRootObject:self];
1266 // NOTE: 'serverName' is a global variable
1267 serverName = [svrName vimStringSave];
1269 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1272 need_maketitle = TRUE;
1274 [self queueMessage:SetServerNameMsgID data:
1275 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1279 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1283 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1284 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1287 // NOTE: If 'name' equals 'serverName' then the request is local (client
1288 // and server are the same). This case is not handled separately, so a
1289 // connection will be set up anyway (this simplifies the code).
1291 NSConnection *conn = [self connectionForServerName:name];
1294 char_u *s = (char_u*)[name UTF8String];
1296 s = CONVERT_FROM_UTF8(s);
1298 EMSG2(_(e_noserver), s);
1300 CONVERT_FROM_UTF8_FREE(s);
1307 // HACK! Assume connection uses mach ports.
1308 *port = [(NSMachPort*)[conn sendPort] machPort];
1311 id proxy = [conn rootProxy];
1312 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1316 NSString *eval = [proxy evaluateExpression:string client:self];
1319 *reply = [eval vimStringSave];
1321 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1328 [proxy addInput:string client:self];
1331 @catch (NSException *e) {
1332 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1339 - (NSArray *)serverList
1341 NSArray *list = nil;
1343 if ([self connection]) {
1344 id proxy = [connection rootProxy];
1345 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1348 list = [proxy serverList];
1350 @catch (NSException *e) {
1351 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1354 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1360 - (NSString *)peekForReplyOnPort:(int)port
1362 //NSLog(@"%s%d", _cmd, port);
1364 NSNumber *key = [NSNumber numberWithInt:port];
1365 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1366 if (replies && [replies count]) {
1367 //NSLog(@" %d replies, topmost is: %@", [replies count],
1368 // [replies objectAtIndex:0]);
1369 return [replies objectAtIndex:0];
1372 //NSLog(@" No replies");
1376 - (NSString *)waitForReplyOnPort:(int)port
1378 //NSLog(@"%s%d", _cmd, port);
1380 NSConnection *conn = [self connectionForServerPort:port];
1384 NSNumber *key = [NSNumber numberWithInt:port];
1385 NSMutableArray *replies = nil;
1386 NSString *reply = nil;
1388 // Wait for reply as long as the connection to the server is valid (unless
1389 // user interrupts wait with Ctrl-C).
1390 while (!got_int && [conn isValid] &&
1391 !(replies = [serverReplyDict objectForKey:key])) {
1392 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1393 beforeDate:[NSDate distantFuture]];
1397 if ([replies count] > 0) {
1398 reply = [[replies objectAtIndex:0] retain];
1399 //NSLog(@" Got reply: %@", reply);
1400 [replies removeObjectAtIndex:0];
1401 [reply autorelease];
1404 if ([replies count] == 0)
1405 [serverReplyDict removeObjectForKey:key];
1411 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1413 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1416 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1417 [client addReply:reply server:self];
1420 @catch (NSException *e) {
1421 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1424 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1435 - (void)setWaitForAck:(BOOL)yn
1440 - (void)waitForConnectionAcknowledgement
1442 if (!waitForAck) return;
1444 while (waitForAck && !got_int && [connection isValid]) {
1445 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1446 beforeDate:[NSDate distantFuture]];
1447 //NSLog(@" waitForAck=%d got_int=%d isValid=%d",
1448 // waitForAck, got_int, [connection isValid]);
1452 // Never received a connection acknowledgement, so die.
1453 [[NSNotificationCenter defaultCenter] removeObserver:self];
1454 [appProxy release]; appProxy = nil;
1456 // NOTE: We intentionally do not call mch_exit() since this in turn
1457 // will lead to -[MMBackend exit] getting called which we want to
1462 [self processInputQueue];
1465 - (oneway void)acknowledgeConnection
1467 //NSLog(@"%s", _cmd);
1475 @implementation MMBackend (Private)
1477 - (void)clearDrawData
1479 [drawData setLength:0];
1480 numWholeLineChanges = offsetForDrawDataPrune = 0;
1483 - (void)didChangeWholeLine
1485 // It may happen that draw queue is filled up with lots of changes that
1486 // affect a whole row. If the number of such changes equals twice the
1487 // number of visible rows then we can prune some commands off the queue.
1489 // NOTE: If we don't perform this pruning the draw queue may grow
1490 // indefinitely if Vim were to repeatedly send draw commands without ever
1491 // waiting for new input (that's when the draw queue is flushed). The one
1492 // instance I know where this can happen is when a command is executed in
1493 // the shell (think ":grep" with thousands of matches).
1495 ++numWholeLineChanges;
1496 if (numWholeLineChanges == gui.num_rows) {
1497 // Remember the offset to prune up to.
1498 offsetForDrawDataPrune = [drawData length];
1499 } else if (numWholeLineChanges == 2*gui.num_rows) {
1500 // Delete all the unnecessary draw commands.
1501 NSMutableData *d = [[NSMutableData alloc]
1502 initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1503 length:[drawData length] - offsetForDrawDataPrune];
1504 offsetForDrawDataPrune = [d length];
1505 numWholeLineChanges -= gui.num_rows;
1511 - (void)waitForDialogReturn
1513 // Keep processing the run loop until a dialog returns. To avoid getting
1514 // stuck in an endless loop (could happen if the setDialogReturn: message
1515 // was lost) we also do some paranoia checks.
1517 // Note that in Cocoa the user can still resize windows and select menu
1518 // items while a sheet is being displayed, so we can't just wait for the
1519 // first message to arrive and assume that is the setDialogReturn: call.
1521 while (nil == dialogReturn && !got_int && [connection isValid])
1522 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1523 beforeDate:[NSDate distantFuture]];
1525 // Search for any resize messages on the input queue. All other messages
1526 // on the input queue are dropped. The reason why we single out resize
1527 // messages is because the user may have resized the window while a sheet
1529 int i, count = [inputQueue count];
1531 id textDimData = nil;
1533 for (i = count-2; i >= 0; i -= 2) {
1534 int msgid = [[inputQueue objectAtIndex:i] intValue];
1535 if (SetTextDimensionsMsgID == msgid) {
1536 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1542 [inputQueue removeAllObjects];
1545 [inputQueue addObject:
1546 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1547 [inputQueue addObject:textDimData];
1548 [textDimData release];
1553 - (void)insertVimStateMessage
1555 // NOTE: This is the place to add Vim state that needs to be accessed from
1556 // MacVim. Do not add state that could potentially require lots of memory
1557 // since this message gets sent each time the output queue is forcibly
1558 // flushed (e.g. storing the currently selected text would be a bad idea).
1559 // We take this approach of "pushing" the state to MacVim to avoid having
1560 // to make synchronous calls from MacVim to Vim in order to get state.
1562 BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1563 int numTabs = tabpage_index(NULL) - 1;
1567 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1568 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1569 [NSNumber numberWithInt:p_mh], @"p_mh",
1570 [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1571 [NSNumber numberWithBool:mmta], @"p_mmta",
1572 [NSNumber numberWithInt:numTabs], @"numTabs",
1575 // Put the state before all other messages.
1576 int msgid = SetVimStateMsgID;
1577 [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1578 [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1582 - (void)processInputQueue
1584 if ([inputQueue count] == 0) return;
1586 // NOTE: One of the input events may cause this method to be called
1587 // recursively, so copy the input queue to a local variable and clear the
1588 // queue before starting to process input events (otherwise we could get
1589 // stuck in an endless loop).
1590 NSArray *q = [inputQueue copy];
1591 unsigned i, count = [q count];
1593 [inputQueue removeAllObjects];
1595 for (i = 1; i < count; i+=2) {
1596 int msgid = [[q objectAtIndex:i-1] intValue];
1597 id data = [q objectAtIndex:i];
1598 if ([data isEqual:[NSNull null]])
1601 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1602 [self handleInputEvent:msgid data:data];
1606 //NSLog(@"Clear input event queue");
1609 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1611 if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1612 CmdKeyMsgID == msgid) {
1614 const void *bytes = [data bytes];
1615 int mods = *((int*)bytes); bytes += sizeof(int);
1616 int len = *((int*)bytes); bytes += sizeof(int);
1617 NSString *key = [[NSString alloc] initWithBytes:bytes
1619 encoding:NSUTF8StringEncoding];
1620 mods = eventModifierFlagsToVimModMask(mods);
1622 if (InsertTextMsgID == msgid)
1623 [self handleInsertText:key];
1625 [self handleKeyDown:key modifiers:mods];
1628 } else if (ScrollWheelMsgID == msgid) {
1630 const void *bytes = [data bytes];
1632 int row = *((int*)bytes); bytes += sizeof(int);
1633 int col = *((int*)bytes); bytes += sizeof(int);
1634 int flags = *((int*)bytes); bytes += sizeof(int);
1635 float dy = *((float*)bytes); bytes += sizeof(float);
1637 int button = MOUSE_5;
1638 if (dy > 0) button = MOUSE_4;
1640 flags = eventModifierFlagsToVimMouseModMask(flags);
1642 int numLines = (int)round(dy);
1643 if (numLines < 0) numLines = -numLines;
1644 if (numLines == 0) numLines = 1;
1646 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1647 gui.scroll_wheel_force = numLines;
1650 gui_send_mouse_event(button, col, row, NO, flags);
1651 } else if (MouseDownMsgID == 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 button = *((int*)bytes); bytes += sizeof(int);
1658 int flags = *((int*)bytes); bytes += sizeof(int);
1659 int count = *((int*)bytes); bytes += sizeof(int);
1661 button = eventButtonNumberToVimMouseButton(button);
1663 flags = eventModifierFlagsToVimMouseModMask(flags);
1664 gui_send_mouse_event(button, col, row, count>1, flags);
1666 } else if (MouseUpMsgID == msgid) {
1668 const void *bytes = [data bytes];
1670 int row = *((int*)bytes); bytes += sizeof(int);
1671 int col = *((int*)bytes); bytes += sizeof(int);
1672 int flags = *((int*)bytes); bytes += sizeof(int);
1674 flags = eventModifierFlagsToVimMouseModMask(flags);
1676 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1677 } else if (MouseDraggedMsgID == msgid) {
1679 const void *bytes = [data bytes];
1681 int row = *((int*)bytes); bytes += sizeof(int);
1682 int col = *((int*)bytes); bytes += sizeof(int);
1683 int flags = *((int*)bytes); bytes += sizeof(int);
1685 flags = eventModifierFlagsToVimMouseModMask(flags);
1687 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1688 } else if (MouseMovedMsgID == msgid) {
1689 const void *bytes = [data bytes];
1690 int row = *((int*)bytes); bytes += sizeof(int);
1691 int col = *((int*)bytes); bytes += sizeof(int);
1693 gui_mouse_moved(col, row);
1694 } else if (AddInputMsgID == msgid) {
1695 NSString *string = [[NSString alloc] initWithData:data
1696 encoding:NSUTF8StringEncoding];
1698 [self addInput:string];
1701 } else if (SelectTabMsgID == msgid) {
1703 const void *bytes = [data bytes];
1704 int idx = *((int*)bytes) + 1;
1705 //NSLog(@"Selecting tab %d", idx);
1706 send_tabline_event(idx);
1707 } else if (CloseTabMsgID == msgid) {
1709 const void *bytes = [data bytes];
1710 int idx = *((int*)bytes) + 1;
1711 //NSLog(@"Closing tab %d", idx);
1712 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1713 } else if (AddNewTabMsgID == msgid) {
1714 //NSLog(@"Adding new tab");
1715 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1716 } else if (DraggedTabMsgID == msgid) {
1718 const void *bytes = [data bytes];
1719 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1721 int idx = *((int*)bytes);
1724 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1725 || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1727 const void *bytes = [data bytes];
1729 if (SetTextColumnsMsgID != msgid) {
1730 rows = *((int*)bytes); bytes += sizeof(int);
1733 if (SetTextRowsMsgID != msgid) {
1734 cols = *((int*)bytes); bytes += sizeof(int);
1738 if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1739 int dim[2] = { rows, cols };
1740 d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1741 msgid = SetTextDimensionsReplyMsgID;
1744 if (SetTextDimensionsMsgID == msgid)
1745 msgid = SetTextDimensionsReplyMsgID;
1747 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1748 // gui_resize_shell(), so we have to manually set the rows and columns
1749 // here since MacVim doesn't change the rows and columns to avoid
1750 // inconsistent states between Vim and MacVim. The message sent back
1751 // indicates that it is a reply to a message that originated in MacVim
1752 // since we need to be able to determine where a message originated.
1753 [self queueMessage:msgid data:d];
1755 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1756 gui_resize_shell(cols, rows);
1757 } else if (ExecuteMenuMsgID == msgid) {
1758 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1760 NSArray *desc = [attrs objectForKey:@"descriptor"];
1761 vimmenu_T *menu = menu_for_descriptor(desc);
1765 } else if (ToggleToolbarMsgID == msgid) {
1766 [self handleToggleToolbar];
1767 } else if (ScrollbarEventMsgID == msgid) {
1768 [self handleScrollbarEvent:data];
1769 } else if (SetFontMsgID == msgid) {
1770 [self handleSetFont:data];
1771 } else if (VimShouldCloseMsgID == msgid) {
1773 } else if (DropFilesMsgID == msgid) {
1774 [self handleDropFiles:data];
1775 } else if (DropStringMsgID == msgid) {
1776 [self handleDropString:data];
1777 } else if (GotFocusMsgID == msgid) {
1779 [self focusChange:YES];
1780 } else if (LostFocusMsgID == msgid) {
1782 [self focusChange:NO];
1783 } else if (SetMouseShapeMsgID == msgid) {
1784 const void *bytes = [data bytes];
1785 int shape = *((int*)bytes); bytes += sizeof(int);
1786 update_mouseshape(shape);
1787 } else if (XcodeModMsgID == msgid) {
1788 [self handleXcodeMod:data];
1789 } else if (OpenWithArgumentsMsgID == msgid) {
1790 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1791 } else if (FindReplaceMsgID == msgid) {
1792 [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1794 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1798 + (NSDictionary *)specialKeys
1800 static NSDictionary *specialKeys = nil;
1803 NSBundle *mainBundle = [NSBundle mainBundle];
1804 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1806 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1812 - (void)handleInsertText:(NSString *)text
1816 char_u *str = (char_u*)[text UTF8String];
1817 int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1820 char_u *conv_str = NULL;
1821 if (input_conv.vc_type != CONV_NONE) {
1822 conv_str = string_convert(&input_conv, str, &len);
1828 for (i = 0; i < len; ++i) {
1829 add_to_input_buf(str+i, 1);
1830 if (CSI == str[i]) {
1831 // NOTE: If the converted string contains the byte CSI, then it
1832 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1834 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1835 add_to_input_buf(extra, 2);
1845 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1847 // TODO: This code is a horrible mess -- clean up!
1850 char_u *chars = (char_u*)[key UTF8String];
1852 char_u *conv_str = NULL;
1854 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1856 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1857 // that new keys can easily be added.
1858 NSString *specialString = [[MMBackend specialKeys]
1860 if (specialString && [specialString length] > 1) {
1861 //NSLog(@"special key: %@", specialString);
1862 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1863 [specialString characterAtIndex:1]);
1865 ikey = simplify_key(ikey, &mods);
1870 special[1] = K_SECOND(ikey);
1871 special[2] = K_THIRD(ikey);
1875 } else if (1 == length && TAB == chars[0]) {
1876 // Tab is a trouble child:
1877 // - <Tab> is added to the input buffer as is
1878 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1879 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1880 // to be converted to utf-8
1881 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1882 // - <C-Tab> is reserved by Mac OS X
1883 // - <D-Tab> is reserved by Mac OS X
1888 if (mods & MOD_MASK_SHIFT) {
1889 mods &= ~MOD_MASK_SHIFT;
1891 special[1] = K_SECOND(K_S_TAB);
1892 special[2] = K_THIRD(K_S_TAB);
1894 } else if (mods & MOD_MASK_ALT) {
1895 int mtab = 0x80 | TAB;
1899 special[0] = (mtab >> 6) + 0xc0;
1900 special[1] = mtab & 0xbf;
1908 mods &= ~MOD_MASK_ALT;
1910 } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1911 // META key is treated separately. This code was taken from gui_w48.c
1912 // and gui_gtk_x11.c.
1914 int ch = simplify_key(chars[0], &mods);
1916 // Remove the SHIFT modifier for keys where it's already included,
1917 // e.g., '(' and '*'
1918 if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1919 mods &= ~MOD_MASK_SHIFT;
1921 // Interpret the ALT key as making the key META, include SHIFT, etc.
1922 ch = extract_modifiers(ch, &mods);
1928 string[len++] = CSI;
1929 string[len++] = KS_MODIFIER;
1930 string[len++] = mods;
1933 if (IS_SPECIAL(ch)) {
1934 string[len++] = CSI;
1935 string[len++] = K_SECOND(ch);
1936 string[len++] = K_THIRD(ch);
1940 // TODO: What if 'enc' is not "utf-8"?
1941 if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1942 string[len++] = ch & 0xbf;
1943 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1944 if (string[len-1] == CSI) {
1945 string[len++] = KS_EXTRA;
1946 string[len++] = (int)KE_CSI;
1952 add_to_input_buf(string, len);
1954 } else if (length > 0) {
1955 unichar c = [key characterAtIndex:0];
1956 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1957 // [key characterAtIndex:0], mods);
1959 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1960 // cleared since they are already added to the key by the AppKit.
1961 // Unfortunately, the only way to deal with when to clear the modifiers
1962 // or not seems to be to have hard-wired rules like this.
1963 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1964 || 0x9 == c || 0xd == c || ESC == c) ) {
1965 mods &= ~MOD_MASK_SHIFT;
1966 mods &= ~MOD_MASK_CTRL;
1967 //NSLog(@"clear shift ctrl");
1971 if (input_conv.vc_type != CONV_NONE) {
1972 conv_str = string_convert(&input_conv, chars, &length);
1979 if (chars && length > 0) {
1981 //NSLog(@"adding mods: %d", mods);
1983 modChars[1] = KS_MODIFIER;
1985 add_to_input_buf(modChars, 3);
1988 //NSLog(@"add to input buf: 0x%x", chars[0]);
1989 // TODO: Check for CSI bytes?
1990 add_to_input_buf(chars, length);
1999 - (void)queueMessage:(int)msgid data:(NSData *)data
2001 //if (msgid != EnableMenuItemMsgID)
2002 // NSLog(@"queueMessage:%s", MessageStrings[msgid]);
2004 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2006 [outputQueue addObject:data];
2008 [outputQueue addObject:[NSData data]];
2011 - (void)connectionDidDie:(NSNotification *)notification
2013 // If the main connection to MacVim is lost this means that either MacVim
2014 // has crashed or this process did not receive its termination message
2015 // properly (e.g. if the TerminateNowMsgID was dropped).
2017 // NOTE: This is not called if a Vim controller invalidates its connection.
2019 NSLog(@"WARNING[%s]: Main connection was lost before process had a chance "
2020 "to terminate; preserving swap files.", _cmd);
2021 getout_preserve_modified(1);
2024 - (void)blinkTimerFired:(NSTimer *)timer
2026 NSTimeInterval timeInterval = 0;
2028 [blinkTimer release];
2031 if (MMBlinkStateOn == blinkState) {
2032 gui_undraw_cursor();
2033 blinkState = MMBlinkStateOff;
2034 timeInterval = blinkOffInterval;
2035 } else if (MMBlinkStateOff == blinkState) {
2036 gui_update_cursor(TRUE, FALSE);
2037 blinkState = MMBlinkStateOn;
2038 timeInterval = blinkOnInterval;
2041 if (timeInterval > 0) {
2043 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2044 selector:@selector(blinkTimerFired:)
2045 userInfo:nil repeats:NO] retain];
2046 [self flushQueue:YES];
2050 - (void)focusChange:(BOOL)on
2052 gui_focus_change(on);
2055 - (void)handleToggleToolbar
2057 // If 'go' contains 'T', then remove it, else add it.
2059 char_u go[sizeof(GO_ALL)+2];
2064 p = vim_strchr(go, GO_TOOLBAR);
2068 char_u *end = go + len;
2074 go[len] = GO_TOOLBAR;
2078 set_option_value((char_u*)"guioptions", 0, go, 0);
2080 [self redrawScreen];
2083 - (void)handleScrollbarEvent:(NSData *)data
2087 const void *bytes = [data bytes];
2088 long ident = *((long*)bytes); bytes += sizeof(long);
2089 int hitPart = *((int*)bytes); bytes += sizeof(int);
2090 float fval = *((float*)bytes); bytes += sizeof(float);
2091 scrollbar_T *sb = gui_find_scrollbar(ident);
2094 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2095 long value = sb_info->value;
2096 long size = sb_info->size;
2097 long max = sb_info->max;
2098 BOOL isStillDragging = NO;
2099 BOOL updateKnob = YES;
2102 case NSScrollerDecrementPage:
2103 value -= (size > 2 ? size - 2 : 1);
2105 case NSScrollerIncrementPage:
2106 value += (size > 2 ? size - 2 : 1);
2108 case NSScrollerDecrementLine:
2111 case NSScrollerIncrementLine:
2114 case NSScrollerKnob:
2115 isStillDragging = YES;
2117 case NSScrollerKnobSlot:
2118 value = (long)(fval * (max - size + 1));
2125 //NSLog(@"value %d -> %d", sb_info->value, value);
2126 gui_drag_scrollbar(sb, value, isStillDragging);
2129 // Dragging the knob or option+clicking automatically updates
2130 // the knob position (on the actual NSScroller), so we only
2131 // need to set the knob position in the other cases.
2133 // Update both the left&right vertical scrollbars.
2134 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2135 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2136 [self setScrollbarThumbValue:value size:size max:max
2137 identifier:identLeft];
2138 [self setScrollbarThumbValue:value size:size max:max
2139 identifier:identRight];
2141 // Update the horizontal scrollbar.
2142 [self setScrollbarThumbValue:value size:size max:max
2149 - (void)handleSetFont:(NSData *)data
2153 const void *bytes = [data bytes];
2154 float pointSize = *((float*)bytes); bytes += sizeof(float);
2155 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2156 bytes += sizeof(unsigned); // len not used
2158 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2159 [name appendString:[NSString stringWithFormat:@":h%d", (int)pointSize]];
2160 char_u *s = (char_u*)[name UTF8String];
2163 s = CONVERT_FROM_UTF8(s);
2166 set_option_value((char_u*)"guifont", 0, s, 0);
2169 CONVERT_FROM_UTF8_FREE(s);
2172 [self redrawScreen];
2175 - (void)handleDropFiles:(NSData *)data
2177 // TODO: Get rid of this method; instead use Vim script directly. At the
2178 // moment I know how to do this to open files in tabs, but I'm not sure how
2179 // to add the filenames to the command line when in command line mode.
2183 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2186 id obj = [args objectForKey:@"forceOpen"];
2187 BOOL forceOpen = YES;
2189 forceOpen = [obj boolValue];
2191 NSArray *filenames = [args objectForKey:@"filenames"];
2192 if (!(filenames && [filenames count] > 0)) return;
2195 if (!forceOpen && (State & CMDLINE)) {
2196 // HACK! If Vim is in command line mode then the files names
2197 // should be added to the command line, instead of opening the
2198 // files in tabs (unless forceOpen is set). This is taken care of by
2199 // gui_handle_drop().
2200 int n = [filenames count];
2201 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2204 for (i = 0; i < n; ++i)
2205 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2207 // NOTE! This function will free 'fnames'.
2208 // HACK! It is assumed that the 'x' and 'y' arguments are
2209 // unused when in command line mode.
2210 gui_handle_drop(0, 0, 0, fnames, n);
2215 [self handleOpenWithArguments:args];
2219 - (void)handleDropString:(NSData *)data
2224 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2225 const void *bytes = [data bytes];
2226 int len = *((int*)bytes); bytes += sizeof(int);
2227 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2229 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2230 NSRange range = { 0, [string length] };
2231 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2232 withString:@"\x0a" options:0
2235 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2236 options:0 range:range];
2239 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2240 char_u *s = (char_u*)[string UTF8String];
2242 if (input_conv.vc_type != CONV_NONE)
2243 s = string_convert(&input_conv, s, &len);
2245 dnd_yank_drag_data(s, len);
2247 if (input_conv.vc_type != CONV_NONE)
2250 add_to_input_buf(dropkey, sizeof(dropkey));
2254 - (void)startOdbEditWithArguments:(NSDictionary *)args
2256 #ifdef FEAT_ODB_EDITOR
2257 id obj = [args objectForKey:@"remoteID"];
2260 OSType serverID = [obj unsignedIntValue];
2261 NSString *remotePath = [args objectForKey:@"remotePath"];
2263 NSAppleEventDescriptor *token = nil;
2264 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2265 obj = [args objectForKey:@"remoteTokenDescType"];
2266 if (tokenData && obj) {
2267 DescType tokenType = [obj unsignedLongValue];
2268 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2272 NSArray *filenames = [args objectForKey:@"filenames"];
2273 unsigned i, numFiles = [filenames count];
2274 for (i = 0; i < numFiles; ++i) {
2275 NSString *filename = [filenames objectAtIndex:i];
2276 char_u *s = [filename vimStringSave];
2277 buf_T *buf = buflist_findname(s);
2281 if (buf->b_odb_token) {
2282 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2283 buf->b_odb_token = NULL;
2286 if (buf->b_odb_fname) {
2287 vim_free(buf->b_odb_fname);
2288 buf->b_odb_fname = NULL;
2291 buf->b_odb_server_id = serverID;
2294 buf->b_odb_token = [token retain];
2296 buf->b_odb_fname = [remotePath vimStringSave];
2298 NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2302 #endif // FEAT_ODB_EDITOR
2305 - (void)handleXcodeMod:(NSData *)data
2308 const void *bytes = [data bytes];
2309 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2310 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2314 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2315 descriptorWithDescriptorType:type
2321 - (void)handleOpenWithArguments:(NSDictionary *)args
2323 // ARGUMENT: DESCRIPTION:
2324 // -------------------------------------------------------------
2325 // filenames list of filenames
2326 // dontOpen don't open files specified in above argument
2327 // layout which layout to use to open files
2328 // selectionRange range of lines to select
2329 // searchText string to search for
2330 // cursorLine line to position the cursor on
2331 // cursorColumn column to position the cursor on
2332 // (only valid when "cursorLine" is set)
2333 // remoteID ODB parameter
2334 // remotePath ODB parameter
2335 // remoteTokenDescType ODB parameter
2336 // remoteTokenData ODB parameter
2338 //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2340 NSArray *filenames = [args objectForKey:@"filenames"];
2341 int i, numFiles = filenames ? [filenames count] : 0;
2342 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2343 int layout = [[args objectForKey:@"layout"] intValue];
2345 // Change to directory of first file to open if this is an "unused" editor
2346 // (but do not do this if editing remotely).
2347 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2348 && (starting || [self unusedEditor]) ) {
2349 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2355 // When Vim is starting we simply add the files to be opened to the
2356 // global arglist and Vim will take care of opening them for us.
2357 if (openFiles && numFiles > 0) {
2358 for (i = 0; i < numFiles; i++) {
2359 NSString *fname = [filenames objectAtIndex:i];
2362 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2363 || (p = [fname vimStringSave]) == NULL)
2364 exit(2); // See comment in -[MMBackend exit]
2366 alist_add(&global_alist, p, 2);
2369 // Vim will take care of arranging the files added to the arglist
2370 // in windows or tabs; all we must do is to specify which layout to
2372 initialWindowLayout = layout;
2375 // When Vim is already open we resort to some trickery to open the
2376 // files with the specified layout.
2378 // TODO: Figure out a better way to handle this?
2379 if (openFiles && numFiles > 0) {
2380 BOOL oneWindowInTab = topframe ? YES
2381 : (topframe->fr_layout == FR_LEAF);
2382 BOOL bufChanged = NO;
2383 BOOL bufHasFilename = NO;
2385 bufChanged = curbufIsChanged();
2386 bufHasFilename = curbuf->b_ffname != NULL;
2389 // Temporarily disable flushing since the following code may
2390 // potentially cause multiple redraws.
2391 flushDisabled = YES;
2393 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2394 if (WIN_TABS == layout && !onlyOneTab) {
2395 // By going to the last tabpage we ensure that the new tabs
2396 // will appear last (if this call is left out, the taborder
2401 // Make sure we're in normal mode first.
2402 [self addInput:@"<C-\\><C-N>"];
2405 // With "split layout" we open a new tab before opening
2406 // multiple files if the current tab has more than one window
2407 // or if there is exactly one window but whose buffer has a
2408 // filename. (The :drop command ensures modified buffers get
2409 // their own window.)
2410 if ((WIN_HOR == layout || WIN_VER == layout) &&
2411 (!oneWindowInTab || bufHasFilename))
2412 [self addInput:@":tabnew<CR>"];
2414 // The files are opened by constructing a ":drop ..." command
2415 // and executing it.
2416 NSMutableString *cmd = (WIN_TABS == layout)
2417 ? [NSMutableString stringWithString:@":tab drop"]
2418 : [NSMutableString stringWithString:@":drop"];
2420 for (i = 0; i < numFiles; ++i) {
2421 NSString *file = [filenames objectAtIndex:i];
2422 file = [file stringByEscapingSpecialFilenameCharacters];
2423 [cmd appendString:@" "];
2424 [cmd appendString:file];
2427 // Temporarily clear 'suffixes' so that the files are opened in
2428 // the same order as they appear in the "filenames" array.
2429 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2431 [self addInput:cmd];
2433 // Split the view into multiple windows if requested.
2434 if (WIN_HOR == layout)
2435 [self addInput:@"|sall"];
2436 else if (WIN_VER == layout)
2437 [self addInput:@"|vert sall"];
2439 // Restore the old value of 'suffixes'.
2440 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2442 // When opening one file we try to reuse the current window,
2443 // but not if its buffer is modified or has a filename.
2444 // However, the 'arglist' layout always opens the file in the
2446 NSString *file = [[filenames lastObject]
2447 stringByEscapingSpecialFilenameCharacters];
2449 if (WIN_HOR == layout) {
2450 if (!(bufHasFilename || bufChanged))
2451 cmd = [NSString stringWithFormat:@":e %@", file];
2453 cmd = [NSString stringWithFormat:@":sp %@", file];
2454 } else if (WIN_VER == layout) {
2455 if (!(bufHasFilename || bufChanged))
2456 cmd = [NSString stringWithFormat:@":e %@", file];
2458 cmd = [NSString stringWithFormat:@":vsp %@", file];
2459 } else if (WIN_TABS == layout) {
2460 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2461 cmd = [NSString stringWithFormat:@":e %@", file];
2463 cmd = [NSString stringWithFormat:@":tabe %@", file];
2465 // (The :drop command will split if there is a modified
2467 cmd = [NSString stringWithFormat:@":drop %@", file];
2470 [self addInput:cmd];
2471 [self addInput:@"<CR>"];
2474 // Force screen redraw (does it have to be this complicated?).
2475 // (This code was taken from the end of gui_handle_drop().)
2476 update_screen(NOT_VALID);
2479 gui_update_cursor(FALSE, FALSE);
2486 if ([args objectForKey:@"remoteID"]) {
2487 // NOTE: We have to delay processing any ODB related arguments since
2488 // the file(s) may not be opened until the input buffer is processed.
2489 [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2494 NSString *lineString = [args objectForKey:@"cursorLine"];
2495 if (lineString && [lineString intValue] > 0) {
2496 NSString *columnString = [args objectForKey:@"cursorColumn"];
2497 if (!(columnString && [columnString intValue] > 0))
2498 columnString = @"1";
2500 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2501 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2502 [self addInput:cmd];
2505 NSString *rangeString = [args objectForKey:@"selectionRange"];
2507 // Build a command line string that will select the given range of
2508 // lines. If range.length == 0, then position the cursor on the given
2509 // line but do not select.
2510 NSRange range = NSRangeFromString(rangeString);
2512 if (range.length > 0) {
2513 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2514 NSMaxRange(range), range.location];
2516 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2520 [self addInput:cmd];
2523 NSString *searchText = [args objectForKey:@"searchText"];
2525 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2530 - (BOOL)checkForModifiedBuffers
2533 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2534 if (bufIsChanged(buf)) {
2542 - (void)addInput:(NSString *)input
2544 // NOTE: This code is essentially identical to server_to_input_buf(),
2545 // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2546 char_u *string = [input vimStringSave];
2547 if (!string) return;
2549 /* Set 'cpoptions' the way we want it.
2550 * B set - backslashes are *not* treated specially
2551 * k set - keycodes are *not* reverse-engineered
2552 * < unset - <Key> sequences *are* interpreted
2553 * The last but one parameter of replace_termcodes() is TRUE so that the
2554 * <lt> sequence is recognised - needed for a real backslash.
2557 char_u *cpo_save = p_cpo;
2558 p_cpo = (char_u *)"Bk";
2559 char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2562 if (*ptr != NUL) /* trailing CTRL-V results in nothing */
2565 * Add the string to the input stream.
2566 * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2568 * First clear typed characters from the typeahead buffer, there could
2569 * be half a mapping there. Then append to the existing string, so
2570 * that multiple commands from a client are concatenated.
2572 if (typebuf.tb_maplen < typebuf.tb_len)
2573 del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2574 (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2576 /* Let input_available() know we inserted text in the typeahead
2578 typebuf_was_filled = TRUE;
2584 - (BOOL)unusedEditor
2586 BOOL oneWindowInTab = topframe ? YES
2587 : (topframe->fr_layout == FR_LEAF);
2588 BOOL bufChanged = NO;
2589 BOOL bufHasFilename = NO;
2591 bufChanged = curbufIsChanged();
2592 bufHasFilename = curbuf->b_ffname != NULL;
2595 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2597 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2600 - (void)redrawScreen
2602 // Force screen redraw (does it have to be this complicated?).
2603 redraw_all_later(CLEAR);
2604 update_screen(NOT_VALID);
2607 gui_update_cursor(FALSE, FALSE);
2609 // HACK! The cursor is not put back at the command line by the above
2610 // "redraw commands". The following test seems to do the trick though.
2611 if (State & CMDLINE)
2615 - (void)handleFindReplace:(NSDictionary *)args
2619 NSString *findString = [args objectForKey:@"find"];
2620 if (!findString) return;
2622 char_u *find = [findString vimStringSave];
2623 char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2624 int flags = [[args objectForKey:@"flags"] intValue];
2626 // NOTE: The flag 0x100 is used to indicate a backward search.
2627 gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2633 @end // MMBackend (Private)
2638 @implementation MMBackend (ClientServer)
2640 - (NSString *)connectionNameFromServerName:(NSString *)name
2642 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2644 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2648 - (NSConnection *)connectionForServerName:(NSString *)name
2650 // TODO: Try 'name%d' if 'name' fails.
2651 NSString *connName = [self connectionNameFromServerName:name];
2652 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2655 svrConn = [NSConnection connectionWithRegisteredName:connName
2657 // Try alternate server...
2658 if (!svrConn && alternateServerName) {
2659 //NSLog(@" trying to connect to alternate server: %@",
2660 // alternateServerName);
2661 connName = [self connectionNameFromServerName:alternateServerName];
2662 svrConn = [NSConnection connectionWithRegisteredName:connName
2666 // Try looking for alternate servers...
2668 //NSLog(@" looking for alternate servers...");
2669 NSString *alt = [self alternateServerNameForName:name];
2670 if (alt != alternateServerName) {
2671 //NSLog(@" found alternate server: %@", string);
2672 [alternateServerName release];
2673 alternateServerName = [alt copy];
2677 // Try alternate server again...
2678 if (!svrConn && alternateServerName) {
2679 //NSLog(@" trying to connect to alternate server: %@",
2680 // alternateServerName);
2681 connName = [self connectionNameFromServerName:alternateServerName];
2682 svrConn = [NSConnection connectionWithRegisteredName:connName
2687 [connectionNameDict setObject:svrConn forKey:connName];
2689 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2690 [[NSNotificationCenter defaultCenter] addObserver:self
2691 selector:@selector(serverConnectionDidDie:)
2692 name:NSConnectionDidDieNotification object:svrConn];
2699 - (NSConnection *)connectionForServerPort:(int)port
2702 NSEnumerator *e = [connectionNameDict objectEnumerator];
2704 while ((conn = [e nextObject])) {
2705 // HACK! Assume connection uses mach ports.
2706 if (port == [(NSMachPort*)[conn sendPort] machPort])
2713 - (void)serverConnectionDidDie:(NSNotification *)notification
2715 //NSLog(@"%s%@", _cmd, notification);
2717 NSConnection *svrConn = [notification object];
2719 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2720 [[NSNotificationCenter defaultCenter]
2722 name:NSConnectionDidDieNotification
2725 [connectionNameDict removeObjectsForKeys:
2726 [connectionNameDict allKeysForObject:svrConn]];
2728 // HACK! Assume connection uses mach ports.
2729 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2730 NSNumber *key = [NSNumber numberWithInt:port];
2732 [clientProxyDict removeObjectForKey:key];
2733 [serverReplyDict removeObjectForKey:key];
2736 - (void)addClient:(NSDistantObject *)client
2738 NSConnection *conn = [client connectionForProxy];
2739 // HACK! Assume connection uses mach ports.
2740 int port = [(NSMachPort*)[conn sendPort] machPort];
2741 NSNumber *key = [NSNumber numberWithInt:port];
2743 if (![clientProxyDict objectForKey:key]) {
2744 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2745 [clientProxyDict setObject:client forKey:key];
2748 // NOTE: 'clientWindow' is a global variable which is used by <client>
2749 clientWindow = port;
2752 - (NSString *)alternateServerNameForName:(NSString *)name
2754 if (!(name && [name length] > 0))
2757 // Only look for alternates if 'name' doesn't end in a digit.
2758 unichar lastChar = [name characterAtIndex:[name length]-1];
2759 if (lastChar >= '0' && lastChar <= '9')
2762 // Look for alternates among all current servers.
2763 NSArray *list = [self serverList];
2764 if (!(list && [list count] > 0))
2767 // Filter out servers starting with 'name' and ending with a number. The
2768 // (?i) pattern ensures that the match is case insensitive.
2769 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2770 NSPredicate *pred = [NSPredicate predicateWithFormat:
2771 @"SELF MATCHES %@", pat];
2772 list = [list filteredArrayUsingPredicate:pred];
2773 if ([list count] > 0) {
2774 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2775 return [list objectAtIndex:0];
2781 @end // MMBackend (ClientServer)
2786 @implementation NSString (MMServerNameCompare)
2787 - (NSComparisonResult)serverNameCompare:(NSString *)string
2789 return [self compare:string
2790 options:NSCaseInsensitiveSearch|NSNumericSearch];
2797 static int eventModifierFlagsToVimModMask(int modifierFlags)
2801 if (modifierFlags & NSShiftKeyMask)
2802 modMask |= MOD_MASK_SHIFT;
2803 if (modifierFlags & NSControlKeyMask)
2804 modMask |= MOD_MASK_CTRL;
2805 if (modifierFlags & NSAlternateKeyMask)
2806 modMask |= MOD_MASK_ALT;
2807 if (modifierFlags & NSCommandKeyMask)
2808 modMask |= MOD_MASK_CMD;
2813 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2817 if (modifierFlags & NSShiftKeyMask)
2818 modMask |= MOUSE_SHIFT;
2819 if (modifierFlags & NSControlKeyMask)
2820 modMask |= MOUSE_CTRL;
2821 if (modifierFlags & NSAlternateKeyMask)
2822 modMask |= MOUSE_ALT;
2827 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2829 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2831 return (buttonNumber >= 0 && buttonNumber < 3)
2832 ? mouseButton[buttonNumber] : -1;
2837 // This function is modeled after the VimToPython function found in if_python.c
2838 // NB This does a deep copy by value, it does not lookup references like the
2839 // VimToPython function does. This is because I didn't want to deal with the
2840 // retain cycles that this would create, and we can cover 99% of the use cases
2841 // by ignoring it. If we ever switch to using GC in MacVim then this
2842 // functionality can be implemented easily.
2843 static id vimToCocoa(typval_T * tv, int depth)
2849 // Avoid infinite recursion
2854 if (tv->v_type == VAR_STRING) {
2855 char_u * val = tv->vval.v_string;
2856 // val can be NULL if the string is empty
2858 result = [NSString string];
2861 val = CONVERT_TO_UTF8(val);
2863 result = [NSString stringWithUTF8String:(char*)val];
2865 CONVERT_TO_UTF8_FREE(val);
2868 } else if (tv->v_type == VAR_NUMBER) {
2869 // looks like sizeof(varnumber_T) is always <= sizeof(long)
2870 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2871 } else if (tv->v_type == VAR_LIST) {
2872 list_T * list = tv->vval.v_list;
2875 NSMutableArray * arr = result = [NSMutableArray array];
2878 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2879 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2880 [arr addObject:newObj];
2883 } else if (tv->v_type == VAR_DICT) {
2884 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2886 if (tv->vval.v_dict != NULL) {
2887 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2888 int todo = ht->ht_used;
2892 for (hi = ht->ht_array; todo > 0; ++hi) {
2893 if (!HASHITEM_EMPTY(hi)) {
2896 di = dict_lookup(hi);
2897 newObj = vimToCocoa(&di->di_tv, depth + 1);
2899 char_u * keyval = hi->hi_key;
2901 keyval = CONVERT_TO_UTF8(keyval);
2903 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2905 CONVERT_TO_UTF8_FREE(keyval);
2907 [dict setObject:newObj forKey:key];
2911 } else { // only func refs should fall into this category?
2919 // This function is modeled after eval_client_expr_to_string found in main.c
2920 // Returns nil if there was an error evaluating the expression, and writes a
2921 // message to errorStr.
2922 // TODO Get the error that occurred while evaluating the expression in vim
2924 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2927 char_u *s = (char_u*)[expr UTF8String];
2930 s = CONVERT_FROM_UTF8(s);
2933 int save_dbl = debug_break_level;
2934 int save_ro = redir_off;
2936 debug_break_level = -1;
2940 typval_T * tvres = eval_expr(s, NULL);
2942 debug_break_level = save_dbl;
2943 redir_off = save_ro;
2950 CONVERT_FROM_UTF8_FREE(s);
2955 gui_update_cursor(FALSE, FALSE);
2958 if (tvres == NULL) {
2960 *errstr = @"Expression evaluation failed.";
2963 id res = vimToCocoa(tvres, 1);
2968 *errstr = @"Conversion to cocoa values failed.";
2976 @implementation NSString (VimStrings)
2978 + (id)stringWithVimString:(char_u *)s
2980 // This method ensures a non-nil string is returned. If 's' cannot be
2981 // converted to a utf-8 string it is assumed to be latin-1. If conversion
2982 // still fails an empty NSString is returned.
2983 NSString *string = nil;
2986 s = CONVERT_TO_UTF8(s);
2988 string = [NSString stringWithUTF8String:(char*)s];
2990 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2992 string = [NSString stringWithCString:(char*)s
2993 encoding:NSISOLatin1StringEncoding];
2996 CONVERT_TO_UTF8_FREE(s);
3000 return string != nil ? string : [NSString string];
3003 - (char_u *)vimStringSave
3005 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3008 s = CONVERT_FROM_UTF8(s);
3010 ret = vim_strsave(s);
3012 CONVERT_FROM_UTF8_FREE(s);
3018 @end // NSString (VimStrings)