1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
6 * Do ":help uganda" in Vim to read copying and usage conditions.
7 * Do ":help credits" in Vim to see a list of people who contributed.
8 * See README.txt for an overview of the Vim source code.
13 * MMBackend communicates with the frontend (MacVim). It maintains a queue of
14 * output which is flushed to the frontend under controlled circumstances (so
15 * as to maintain a steady framerate). Input from the frontend is also handled
18 * The frontend communicates with the backend via the MMBackendProtocol. In
19 * particular, input is sent to the backend via processInput:data: and Vim
20 * state can be queried from the frontend with evaluateExpression:.
22 * It is very important to realize that all state is held by the backend, the
23 * frontend must either ask for state [MMBackend evaluateExpression:] or wait
24 * for the backend to update [MMVimController processCommandQueue:].
26 * The client/server functionality of Vim is handled by the backend. It sets
27 * up a named NSConnection to which other Vim processes can connect.
34 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
35 // whereas colors in Vim are int without the alpha component. Also note that
36 // 'transp' is assumed to be a value between 0 and 100.
37 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
38 #define MM_COLOR_WITH_TRANSP(col,transp) \
39 ((unsigned)( ((col)&0xffffff) \
40 | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
42 // Values for window layout (must match values in main.c).
43 #define WIN_HOR 1 // "-o" horizontally split windows
44 #define WIN_VER 2 // "-O" vertically split windows
45 #define WIN_TABS 3 // "-p" windows on tab pages
47 static unsigned MMServerMax = 1000;
49 // TODO: Move to separate file.
50 static int eventModifierFlagsToVimModMask(int modifierFlags);
51 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
52 static int eventButtonNumberToVimMouseButton(int buttonNumber);
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
696 [frontendProxy showSavePanelWithAttributes:attr];
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(@"Exception caught when showing save panel: \"%@\"", 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
736 [frontendProxy presentDialogWithAttributes:attr];
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(@"Exception caught while showing alert dialog: \"%@\"", 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 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1111 // This is just a convenience method that allows the frontend to delay
1112 // sending messages.
1113 int i, count = [messages count];
1114 for (i = 1; i < count; i+=2)
1115 [self processInput:[[messages objectAtIndex:i-1] intValue]
1116 data:[messages objectAtIndex:i]];
1119 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1120 errorString:(out bycopy NSString **)errstr
1122 return evalExprCocoa(expr, errstr);
1126 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1128 NSString *eval = nil;
1129 char_u *s = (char_u*)[expr UTF8String];
1132 s = CONVERT_FROM_UTF8(s);
1135 char_u *res = eval_client_expr_to_string(s);
1138 CONVERT_FROM_UTF8_FREE(s);
1144 s = CONVERT_TO_UTF8(s);
1146 eval = [NSString stringWithUTF8String:(char*)s];
1148 CONVERT_TO_UTF8_FREE(s);
1156 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1158 // TODO: This method should share code with clip_mch_request_selection().
1160 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1161 // If there is no pasteboard, return YES to indicate that there is text
1166 clip_copy_selection();
1168 // Get the text to put on the pasteboard.
1169 long_u llen = 0; char_u *str = 0;
1170 int type = clip_convert_selection(&str, &llen, &clip_star);
1174 // TODO: Avoid overflow.
1175 int len = (int)llen;
1177 if (output_conv.vc_type != CONV_NONE) {
1178 char_u *conv_str = string_convert(&output_conv, str, &len);
1186 NSString *string = [[NSString alloc]
1187 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1189 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1190 [pboard declareTypes:types owner:nil];
1191 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1202 - (oneway void)addReply:(in bycopy NSString *)reply
1203 server:(in byref id <MMVimServerProtocol>)server
1205 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1207 // Replies might come at any time and in any order so we keep them in an
1208 // array inside a dictionary with the send port used as key.
1210 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1211 // HACK! Assume connection uses mach ports.
1212 int port = [(NSMachPort*)[conn sendPort] machPort];
1213 NSNumber *key = [NSNumber numberWithInt:port];
1215 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1217 replies = [NSMutableArray array];
1218 [serverReplyDict setObject:replies forKey:key];
1221 [replies addObject:reply];
1224 - (void)addInput:(in bycopy NSString *)input
1225 client:(in byref id <MMVimClientProtocol>)client
1227 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1229 // NOTE: We don't call addInput: here because it differs from
1230 // server_to_input_buf() in that it always sets the 'silent' flag and we
1231 // don't want the MacVim client/server code to behave differently from
1233 char_u *s = [input vimStringSave];
1234 server_to_input_buf(s);
1237 [self addClient:(id)client];
1240 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1241 client:(in byref id <MMVimClientProtocol>)client
1243 [self addClient:(id)client];
1244 return [self evaluateExpression:expr];
1247 - (void)registerServerWithName:(NSString *)name
1249 NSString *svrName = name;
1250 NSConnection *svrConn = [NSConnection defaultConnection];
1253 for (i = 0; i < MMServerMax; ++i) {
1254 NSString *connName = [self connectionNameFromServerName:svrName];
1256 if ([svrConn registerName:connName]) {
1257 //NSLog(@"Registered server with name: %@", svrName);
1259 // TODO: Set request/reply time-outs to something else?
1261 // Don't wait for requests (time-out means that the message is
1263 [svrConn setRequestTimeout:0];
1264 //[svrConn setReplyTimeout:MMReplyTimeout];
1265 [svrConn setRootObject:self];
1267 // NOTE: 'serverName' is a global variable
1268 serverName = [svrName vimStringSave];
1270 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1273 need_maketitle = TRUE;
1275 [self queueMessage:SetServerNameMsgID data:
1276 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1280 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1284 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1285 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1288 // NOTE: If 'name' equals 'serverName' then the request is local (client
1289 // and server are the same). This case is not handled separately, so a
1290 // connection will be set up anyway (this simplifies the code).
1292 NSConnection *conn = [self connectionForServerName:name];
1295 char_u *s = (char_u*)[name UTF8String];
1297 s = CONVERT_FROM_UTF8(s);
1299 EMSG2(_(e_noserver), s);
1301 CONVERT_FROM_UTF8_FREE(s);
1308 // HACK! Assume connection uses mach ports.
1309 *port = [(NSMachPort*)[conn sendPort] machPort];
1312 id proxy = [conn rootProxy];
1313 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1317 NSString *eval = [proxy evaluateExpression:string client:self];
1320 *reply = [eval vimStringSave];
1322 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1329 [proxy addInput:string client:self];
1332 @catch (NSException *e) {
1333 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1340 - (NSArray *)serverList
1342 NSArray *list = nil;
1344 if ([self connection]) {
1345 id proxy = [connection rootProxy];
1346 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1349 list = [proxy serverList];
1351 @catch (NSException *e) {
1352 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1355 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1361 - (NSString *)peekForReplyOnPort:(int)port
1363 //NSLog(@"%s%d", _cmd, port);
1365 NSNumber *key = [NSNumber numberWithInt:port];
1366 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1367 if (replies && [replies count]) {
1368 //NSLog(@" %d replies, topmost is: %@", [replies count],
1369 // [replies objectAtIndex:0]);
1370 return [replies objectAtIndex:0];
1373 //NSLog(@" No replies");
1377 - (NSString *)waitForReplyOnPort:(int)port
1379 //NSLog(@"%s%d", _cmd, port);
1381 NSConnection *conn = [self connectionForServerPort:port];
1385 NSNumber *key = [NSNumber numberWithInt:port];
1386 NSMutableArray *replies = nil;
1387 NSString *reply = nil;
1389 // Wait for reply as long as the connection to the server is valid (unless
1390 // user interrupts wait with Ctrl-C).
1391 while (!got_int && [conn isValid] &&
1392 !(replies = [serverReplyDict objectForKey:key])) {
1393 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1394 beforeDate:[NSDate distantFuture]];
1398 if ([replies count] > 0) {
1399 reply = [[replies objectAtIndex:0] retain];
1400 //NSLog(@" Got reply: %@", reply);
1401 [replies removeObjectAtIndex:0];
1402 [reply autorelease];
1405 if ([replies count] == 0)
1406 [serverReplyDict removeObjectForKey:key];
1412 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1414 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1417 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1418 [client addReply:reply server:self];
1421 @catch (NSException *e) {
1422 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1425 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1436 - (void)setWaitForAck:(BOOL)yn
1441 - (void)waitForConnectionAcknowledgement
1443 if (!waitForAck) return;
1445 while (waitForAck && !got_int && [connection isValid]) {
1446 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1447 beforeDate:[NSDate distantFuture]];
1448 //NSLog(@" waitForAck=%d got_int=%d isValid=%d",
1449 // waitForAck, got_int, [connection isValid]);
1453 // Never received a connection acknowledgement, so die.
1454 [[NSNotificationCenter defaultCenter] removeObserver:self];
1455 [appProxy release]; appProxy = nil;
1457 // NOTE: We intentionally do not call mch_exit() since this in turn
1458 // will lead to -[MMBackend exit] getting called which we want to
1463 [self processInputQueue];
1466 - (oneway void)acknowledgeConnection
1468 //NSLog(@"%s", _cmd);
1476 @implementation MMBackend (Private)
1478 - (void)clearDrawData
1480 [drawData setLength:0];
1481 numWholeLineChanges = offsetForDrawDataPrune = 0;
1484 - (void)didChangeWholeLine
1486 // It may happen that draw queue is filled up with lots of changes that
1487 // affect a whole row. If the number of such changes equals twice the
1488 // number of visible rows then we can prune some commands off the queue.
1490 // NOTE: If we don't perform this pruning the draw queue may grow
1491 // indefinitely if Vim were to repeatedly send draw commands without ever
1492 // waiting for new input (that's when the draw queue is flushed). The one
1493 // instance I know where this can happen is when a command is executed in
1494 // the shell (think ":grep" with thousands of matches).
1496 ++numWholeLineChanges;
1497 if (numWholeLineChanges == gui.num_rows) {
1498 // Remember the offset to prune up to.
1499 offsetForDrawDataPrune = [drawData length];
1500 } else if (numWholeLineChanges == 2*gui.num_rows) {
1501 // Delete all the unnecessary draw commands.
1502 NSMutableData *d = [[NSMutableData alloc]
1503 initWithBytes:[drawData bytes] + offsetForDrawDataPrune
1504 length:[drawData length] - offsetForDrawDataPrune];
1505 offsetForDrawDataPrune = [d length];
1506 numWholeLineChanges -= gui.num_rows;
1512 - (void)waitForDialogReturn
1514 // Keep processing the run loop until a dialog returns. To avoid getting
1515 // stuck in an endless loop (could happen if the setDialogReturn: message
1516 // was lost) we also do some paranoia checks.
1518 // Note that in Cocoa the user can still resize windows and select menu
1519 // items while a sheet is being displayed, so we can't just wait for the
1520 // first message to arrive and assume that is the setDialogReturn: call.
1522 while (nil == dialogReturn && !got_int && [connection isValid])
1523 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1524 beforeDate:[NSDate distantFuture]];
1526 // Search for any resize messages on the input queue. All other messages
1527 // on the input queue are dropped. The reason why we single out resize
1528 // messages is because the user may have resized the window while a sheet
1530 int i, count = [inputQueue count];
1532 id textDimData = nil;
1534 for (i = count-2; i >= 0; i -= 2) {
1535 int msgid = [[inputQueue objectAtIndex:i] intValue];
1536 if (SetTextDimensionsMsgID == msgid) {
1537 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1543 [inputQueue removeAllObjects];
1546 [inputQueue addObject:
1547 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1548 [inputQueue addObject:textDimData];
1549 [textDimData release];
1554 - (void)insertVimStateMessage
1556 // NOTE: This is the place to add Vim state that needs to be accessed from
1557 // MacVim. Do not add state that could potentially require lots of memory
1558 // since this message gets sent each time the output queue is forcibly
1559 // flushed (e.g. storing the currently selected text would be a bad idea).
1560 // We take this approach of "pushing" the state to MacVim to avoid having
1561 // to make synchronous calls from MacVim to Vim in order to get state.
1563 BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1564 int numTabs = tabpage_index(NULL) - 1;
1568 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1569 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1570 [NSNumber numberWithInt:p_mh], @"p_mh",
1571 [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1572 [NSNumber numberWithBool:mmta], @"p_mmta",
1573 [NSNumber numberWithInt:numTabs], @"numTabs",
1576 // Put the state before all other messages.
1577 int msgid = SetVimStateMsgID;
1578 [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1579 [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1583 - (void)processInputQueue
1585 if ([inputQueue count] == 0) return;
1587 // NOTE: One of the input events may cause this method to be called
1588 // recursively, so copy the input queue to a local variable and clear the
1589 // queue before starting to process input events (otherwise we could get
1590 // stuck in an endless loop).
1591 NSArray *q = [inputQueue copy];
1592 unsigned i, count = [q count];
1594 [inputQueue removeAllObjects];
1596 for (i = 1; i < count; i+=2) {
1597 int msgid = [[q objectAtIndex:i-1] intValue];
1598 id data = [q objectAtIndex:i];
1599 if ([data isEqual:[NSNull null]])
1602 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1603 [self handleInputEvent:msgid data:data];
1607 //NSLog(@"Clear input event queue");
1610 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1612 if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1613 CmdKeyMsgID == msgid) {
1615 const void *bytes = [data bytes];
1616 int mods = *((int*)bytes); bytes += sizeof(int);
1617 int len = *((int*)bytes); bytes += sizeof(int);
1618 NSString *key = [[NSString alloc] initWithBytes:bytes
1620 encoding:NSUTF8StringEncoding];
1621 mods = eventModifierFlagsToVimModMask(mods);
1623 if (InsertTextMsgID == msgid)
1624 [self handleInsertText:key];
1626 [self handleKeyDown:key modifiers:mods];
1629 } else if (ScrollWheelMsgID == msgid) {
1631 const void *bytes = [data bytes];
1633 int row = *((int*)bytes); bytes += sizeof(int);
1634 int col = *((int*)bytes); bytes += sizeof(int);
1635 int flags = *((int*)bytes); bytes += sizeof(int);
1636 float dy = *((float*)bytes); bytes += sizeof(float);
1638 int button = MOUSE_5;
1639 if (dy > 0) button = MOUSE_4;
1641 flags = eventModifierFlagsToVimMouseModMask(flags);
1643 int numLines = (int)round(dy);
1644 if (numLines < 0) numLines = -numLines;
1645 if (numLines == 0) numLines = 1;
1647 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1648 gui.scroll_wheel_force = numLines;
1651 gui_send_mouse_event(button, col, row, NO, flags);
1652 } else if (MouseDownMsgID == msgid) {
1654 const void *bytes = [data bytes];
1656 int row = *((int*)bytes); bytes += sizeof(int);
1657 int col = *((int*)bytes); bytes += sizeof(int);
1658 int button = *((int*)bytes); bytes += sizeof(int);
1659 int flags = *((int*)bytes); bytes += sizeof(int);
1660 int count = *((int*)bytes); bytes += sizeof(int);
1662 button = eventButtonNumberToVimMouseButton(button);
1664 flags = eventModifierFlagsToVimMouseModMask(flags);
1665 gui_send_mouse_event(button, col, row, count>1, flags);
1667 } else if (MouseUpMsgID == msgid) {
1669 const void *bytes = [data bytes];
1671 int row = *((int*)bytes); bytes += sizeof(int);
1672 int col = *((int*)bytes); bytes += sizeof(int);
1673 int flags = *((int*)bytes); bytes += sizeof(int);
1675 flags = eventModifierFlagsToVimMouseModMask(flags);
1677 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1678 } else if (MouseDraggedMsgID == msgid) {
1680 const void *bytes = [data bytes];
1682 int row = *((int*)bytes); bytes += sizeof(int);
1683 int col = *((int*)bytes); bytes += sizeof(int);
1684 int flags = *((int*)bytes); bytes += sizeof(int);
1686 flags = eventModifierFlagsToVimMouseModMask(flags);
1688 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1689 } else if (MouseMovedMsgID == msgid) {
1690 const void *bytes = [data bytes];
1691 int row = *((int*)bytes); bytes += sizeof(int);
1692 int col = *((int*)bytes); bytes += sizeof(int);
1694 gui_mouse_moved(col, row);
1695 } else if (AddInputMsgID == msgid) {
1696 NSString *string = [[NSString alloc] initWithData:data
1697 encoding:NSUTF8StringEncoding];
1699 [self addInput:string];
1702 } else if (SelectTabMsgID == msgid) {
1704 const void *bytes = [data bytes];
1705 int idx = *((int*)bytes) + 1;
1706 //NSLog(@"Selecting tab %d", idx);
1707 send_tabline_event(idx);
1708 } else if (CloseTabMsgID == msgid) {
1710 const void *bytes = [data bytes];
1711 int idx = *((int*)bytes) + 1;
1712 //NSLog(@"Closing tab %d", idx);
1713 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1714 } else if (AddNewTabMsgID == msgid) {
1715 //NSLog(@"Adding new tab");
1716 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1717 } else if (DraggedTabMsgID == msgid) {
1719 const void *bytes = [data bytes];
1720 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1722 int idx = *((int*)bytes);
1725 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1726 || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1728 const void *bytes = [data bytes];
1730 if (SetTextColumnsMsgID != msgid) {
1731 rows = *((int*)bytes); bytes += sizeof(int);
1734 if (SetTextRowsMsgID != msgid) {
1735 cols = *((int*)bytes); bytes += sizeof(int);
1739 if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1740 int dim[2] = { rows, cols };
1741 d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1742 msgid = SetTextDimensionsReplyMsgID;
1745 if (SetTextDimensionsMsgID == msgid)
1746 msgid = SetTextDimensionsReplyMsgID;
1748 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1749 // gui_resize_shell(), so we have to manually set the rows and columns
1750 // here since MacVim doesn't change the rows and columns to avoid
1751 // inconsistent states between Vim and MacVim. The message sent back
1752 // indicates that it is a reply to a message that originated in MacVim
1753 // since we need to be able to determine where a message originated.
1754 [self queueMessage:msgid data:d];
1756 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1757 gui_resize_shell(cols, rows);
1758 } else if (ExecuteMenuMsgID == msgid) {
1759 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1761 NSArray *desc = [attrs objectForKey:@"descriptor"];
1762 vimmenu_T *menu = menu_for_descriptor(desc);
1766 } else if (ToggleToolbarMsgID == msgid) {
1767 [self handleToggleToolbar];
1768 } else if (ScrollbarEventMsgID == msgid) {
1769 [self handleScrollbarEvent:data];
1770 } else if (SetFontMsgID == msgid) {
1771 [self handleSetFont:data];
1772 } else if (VimShouldCloseMsgID == msgid) {
1774 } else if (DropFilesMsgID == msgid) {
1775 [self handleDropFiles:data];
1776 } else if (DropStringMsgID == msgid) {
1777 [self handleDropString:data];
1778 } else if (GotFocusMsgID == msgid) {
1780 [self focusChange:YES];
1781 } else if (LostFocusMsgID == msgid) {
1783 [self focusChange:NO];
1784 } else if (SetMouseShapeMsgID == msgid) {
1785 const void *bytes = [data bytes];
1786 int shape = *((int*)bytes); bytes += sizeof(int);
1787 update_mouseshape(shape);
1788 } else if (XcodeModMsgID == msgid) {
1789 [self handleXcodeMod:data];
1790 } else if (OpenWithArgumentsMsgID == msgid) {
1791 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1792 } else if (FindReplaceMsgID == msgid) {
1793 [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1795 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1799 + (NSDictionary *)specialKeys
1801 static NSDictionary *specialKeys = nil;
1804 NSBundle *mainBundle = [NSBundle mainBundle];
1805 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1807 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1813 - (void)handleInsertText:(NSString *)text
1817 char_u *str = (char_u*)[text UTF8String];
1818 int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1821 char_u *conv_str = NULL;
1822 if (input_conv.vc_type != CONV_NONE) {
1823 conv_str = string_convert(&input_conv, str, &len);
1829 for (i = 0; i < len; ++i) {
1830 add_to_input_buf(str+i, 1);
1831 if (CSI == str[i]) {
1832 // NOTE: If the converted string contains the byte CSI, then it
1833 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1835 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1836 add_to_input_buf(extra, 2);
1846 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1848 // TODO: This code is a horrible mess -- clean up!
1851 char_u *chars = (char_u*)[key UTF8String];
1853 char_u *conv_str = NULL;
1855 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1857 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1858 // that new keys can easily be added.
1859 NSString *specialString = [[MMBackend specialKeys]
1861 if (specialString && [specialString length] > 1) {
1862 //NSLog(@"special key: %@", specialString);
1863 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1864 [specialString characterAtIndex:1]);
1866 ikey = simplify_key(ikey, &mods);
1871 special[1] = K_SECOND(ikey);
1872 special[2] = K_THIRD(ikey);
1876 } else if (1 == length && TAB == chars[0]) {
1877 // Tab is a trouble child:
1878 // - <Tab> is added to the input buffer as is
1879 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1880 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1881 // to be converted to utf-8
1882 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1883 // - <C-Tab> is reserved by Mac OS X
1884 // - <D-Tab> is reserved by Mac OS X
1889 if (mods & MOD_MASK_SHIFT) {
1890 mods &= ~MOD_MASK_SHIFT;
1892 special[1] = K_SECOND(K_S_TAB);
1893 special[2] = K_THIRD(K_S_TAB);
1895 } else if (mods & MOD_MASK_ALT) {
1896 int mtab = 0x80 | TAB;
1900 special[0] = (mtab >> 6) + 0xc0;
1901 special[1] = mtab & 0xbf;
1909 mods &= ~MOD_MASK_ALT;
1911 } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1912 // META key is treated separately. This code was taken from gui_w48.c
1913 // and gui_gtk_x11.c.
1915 int ch = simplify_key(chars[0], &mods);
1917 // Remove the SHIFT modifier for keys where it's already included,
1918 // e.g., '(' and '*'
1919 if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1920 mods &= ~MOD_MASK_SHIFT;
1922 // Interpret the ALT key as making the key META, include SHIFT, etc.
1923 ch = extract_modifiers(ch, &mods);
1929 string[len++] = CSI;
1930 string[len++] = KS_MODIFIER;
1931 string[len++] = mods;
1934 if (IS_SPECIAL(ch)) {
1935 string[len++] = CSI;
1936 string[len++] = K_SECOND(ch);
1937 string[len++] = K_THIRD(ch);
1941 // TODO: What if 'enc' is not "utf-8"?
1942 if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1943 string[len++] = ch & 0xbf;
1944 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1945 if (string[len-1] == CSI) {
1946 string[len++] = KS_EXTRA;
1947 string[len++] = (int)KE_CSI;
1953 add_to_input_buf(string, len);
1955 } else if (length > 0) {
1956 unichar c = [key characterAtIndex:0];
1957 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1958 // [key characterAtIndex:0], mods);
1960 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1961 // cleared since they are already added to the key by the AppKit.
1962 // Unfortunately, the only way to deal with when to clear the modifiers
1963 // or not seems to be to have hard-wired rules like this.
1964 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1965 || 0x9 == c || 0xd == c || ESC == c) ) {
1966 mods &= ~MOD_MASK_SHIFT;
1967 mods &= ~MOD_MASK_CTRL;
1968 //NSLog(@"clear shift ctrl");
1972 if (input_conv.vc_type != CONV_NONE) {
1973 conv_str = string_convert(&input_conv, chars, &length);
1980 if (chars && length > 0) {
1982 //NSLog(@"adding mods: %d", mods);
1984 modChars[1] = KS_MODIFIER;
1986 add_to_input_buf(modChars, 3);
1989 //NSLog(@"add to input buf: 0x%x", chars[0]);
1990 // TODO: Check for CSI bytes?
1991 add_to_input_buf(chars, length);
2000 - (void)queueMessage:(int)msgid data:(NSData *)data
2002 //if (msgid != EnableMenuItemMsgID)
2003 // NSLog(@"queueMessage:%s", MessageStrings[msgid]);
2005 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
2007 [outputQueue addObject:data];
2009 [outputQueue addObject:[NSData data]];
2012 - (void)connectionDidDie:(NSNotification *)notification
2014 // If the main connection to MacVim is lost this means that either MacVim
2015 // has crashed or this process did not receive its termination message
2016 // properly (e.g. if the TerminateNowMsgID was dropped).
2018 // NOTE: This is not called if a Vim controller invalidates its connection.
2020 NSLog(@"WARNING[%s]: Main connection was lost before process had a chance "
2021 "to terminate; preserving swap files.", _cmd);
2022 getout_preserve_modified(1);
2025 - (void)blinkTimerFired:(NSTimer *)timer
2027 NSTimeInterval timeInterval = 0;
2029 [blinkTimer release];
2032 if (MMBlinkStateOn == blinkState) {
2033 gui_undraw_cursor();
2034 blinkState = MMBlinkStateOff;
2035 timeInterval = blinkOffInterval;
2036 } else if (MMBlinkStateOff == blinkState) {
2037 gui_update_cursor(TRUE, FALSE);
2038 blinkState = MMBlinkStateOn;
2039 timeInterval = blinkOnInterval;
2042 if (timeInterval > 0) {
2044 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2045 selector:@selector(blinkTimerFired:)
2046 userInfo:nil repeats:NO] retain];
2047 [self flushQueue:YES];
2051 - (void)focusChange:(BOOL)on
2053 gui_focus_change(on);
2056 - (void)handleToggleToolbar
2058 // If 'go' contains 'T', then remove it, else add it.
2060 char_u go[sizeof(GO_ALL)+2];
2065 p = vim_strchr(go, GO_TOOLBAR);
2069 char_u *end = go + len;
2075 go[len] = GO_TOOLBAR;
2079 set_option_value((char_u*)"guioptions", 0, go, 0);
2081 [self redrawScreen];
2084 - (void)handleScrollbarEvent:(NSData *)data
2088 const void *bytes = [data bytes];
2089 long ident = *((long*)bytes); bytes += sizeof(long);
2090 int hitPart = *((int*)bytes); bytes += sizeof(int);
2091 float fval = *((float*)bytes); bytes += sizeof(float);
2092 scrollbar_T *sb = gui_find_scrollbar(ident);
2095 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2096 long value = sb_info->value;
2097 long size = sb_info->size;
2098 long max = sb_info->max;
2099 BOOL isStillDragging = NO;
2100 BOOL updateKnob = YES;
2103 case NSScrollerDecrementPage:
2104 value -= (size > 2 ? size - 2 : 1);
2106 case NSScrollerIncrementPage:
2107 value += (size > 2 ? size - 2 : 1);
2109 case NSScrollerDecrementLine:
2112 case NSScrollerIncrementLine:
2115 case NSScrollerKnob:
2116 isStillDragging = YES;
2118 case NSScrollerKnobSlot:
2119 value = (long)(fval * (max - size + 1));
2126 //NSLog(@"value %d -> %d", sb_info->value, value);
2127 gui_drag_scrollbar(sb, value, isStillDragging);
2130 // Dragging the knob or option+clicking automatically updates
2131 // the knob position (on the actual NSScroller), so we only
2132 // need to set the knob position in the other cases.
2134 // Update both the left&right vertical scrollbars.
2135 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2136 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2137 [self setScrollbarThumbValue:value size:size max:max
2138 identifier:identLeft];
2139 [self setScrollbarThumbValue:value size:size max:max
2140 identifier:identRight];
2142 // Update the horizontal scrollbar.
2143 [self setScrollbarThumbValue:value size:size max:max
2150 - (void)handleSetFont:(NSData *)data
2154 const void *bytes = [data bytes];
2155 float pointSize = *((float*)bytes); bytes += sizeof(float);
2156 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2157 bytes += sizeof(unsigned); // len not used
2159 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2160 [name appendString:[NSString stringWithFormat:@":h%d", (int)pointSize]];
2161 char_u *s = (char_u*)[name UTF8String];
2164 s = CONVERT_FROM_UTF8(s);
2167 set_option_value((char_u*)"guifont", 0, s, 0);
2170 CONVERT_FROM_UTF8_FREE(s);
2173 [self redrawScreen];
2176 - (void)handleDropFiles:(NSData *)data
2178 // TODO: Get rid of this method; instead use Vim script directly. At the
2179 // moment I know how to do this to open files in tabs, but I'm not sure how
2180 // to add the filenames to the command line when in command line mode.
2184 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2187 id obj = [args objectForKey:@"forceOpen"];
2188 BOOL forceOpen = YES;
2190 forceOpen = [obj boolValue];
2192 NSArray *filenames = [args objectForKey:@"filenames"];
2193 if (!(filenames && [filenames count] > 0)) return;
2196 if (!forceOpen && (State & CMDLINE)) {
2197 // HACK! If Vim is in command line mode then the files names
2198 // should be added to the command line, instead of opening the
2199 // files in tabs (unless forceOpen is set). This is taken care of by
2200 // gui_handle_drop().
2201 int n = [filenames count];
2202 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2205 for (i = 0; i < n; ++i)
2206 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2208 // NOTE! This function will free 'fnames'.
2209 // HACK! It is assumed that the 'x' and 'y' arguments are
2210 // unused when in command line mode.
2211 gui_handle_drop(0, 0, 0, fnames, n);
2216 [self handleOpenWithArguments:args];
2220 - (void)handleDropString:(NSData *)data
2225 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2226 const void *bytes = [data bytes];
2227 int len = *((int*)bytes); bytes += sizeof(int);
2228 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2230 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2231 NSRange range = { 0, [string length] };
2232 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2233 withString:@"\x0a" options:0
2236 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2237 options:0 range:range];
2240 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2241 char_u *s = (char_u*)[string UTF8String];
2243 if (input_conv.vc_type != CONV_NONE)
2244 s = string_convert(&input_conv, s, &len);
2246 dnd_yank_drag_data(s, len);
2248 if (input_conv.vc_type != CONV_NONE)
2251 add_to_input_buf(dropkey, sizeof(dropkey));
2255 - (void)startOdbEditWithArguments:(NSDictionary *)args
2257 #ifdef FEAT_ODB_EDITOR
2258 id obj = [args objectForKey:@"remoteID"];
2261 OSType serverID = [obj unsignedIntValue];
2262 NSString *remotePath = [args objectForKey:@"remotePath"];
2264 NSAppleEventDescriptor *token = nil;
2265 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2266 obj = [args objectForKey:@"remoteTokenDescType"];
2267 if (tokenData && obj) {
2268 DescType tokenType = [obj unsignedLongValue];
2269 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2273 NSArray *filenames = [args objectForKey:@"filenames"];
2274 unsigned i, numFiles = [filenames count];
2275 for (i = 0; i < numFiles; ++i) {
2276 NSString *filename = [filenames objectAtIndex:i];
2277 char_u *s = [filename vimStringSave];
2278 buf_T *buf = buflist_findname(s);
2282 if (buf->b_odb_token) {
2283 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2284 buf->b_odb_token = NULL;
2287 if (buf->b_odb_fname) {
2288 vim_free(buf->b_odb_fname);
2289 buf->b_odb_fname = NULL;
2292 buf->b_odb_server_id = serverID;
2295 buf->b_odb_token = [token retain];
2297 buf->b_odb_fname = [remotePath vimStringSave];
2299 NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2303 #endif // FEAT_ODB_EDITOR
2306 - (void)handleXcodeMod:(NSData *)data
2309 const void *bytes = [data bytes];
2310 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2311 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2315 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2316 descriptorWithDescriptorType:type
2322 - (void)handleOpenWithArguments:(NSDictionary *)args
2324 // ARGUMENT: DESCRIPTION:
2325 // -------------------------------------------------------------
2326 // filenames list of filenames
2327 // dontOpen don't open files specified in above argument
2328 // layout which layout to use to open files
2329 // selectionRange range of lines to select
2330 // searchText string to search for
2331 // cursorLine line to position the cursor on
2332 // cursorColumn column to position the cursor on
2333 // (only valid when "cursorLine" is set)
2334 // remoteID ODB parameter
2335 // remotePath ODB parameter
2336 // remoteTokenDescType ODB parameter
2337 // remoteTokenData ODB parameter
2339 //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2341 NSArray *filenames = [args objectForKey:@"filenames"];
2342 int i, numFiles = filenames ? [filenames count] : 0;
2343 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2344 int layout = [[args objectForKey:@"layout"] intValue];
2346 // Change to directory of first file to open if this is an "unused" editor
2347 // (but do not do this if editing remotely).
2348 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2349 && (starting || [self unusedEditor]) ) {
2350 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2356 // When Vim is starting we simply add the files to be opened to the
2357 // global arglist and Vim will take care of opening them for us.
2358 if (openFiles && numFiles > 0) {
2359 for (i = 0; i < numFiles; i++) {
2360 NSString *fname = [filenames objectAtIndex:i];
2363 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2364 || (p = [fname vimStringSave]) == NULL)
2365 exit(2); // See comment in -[MMBackend exit]
2367 alist_add(&global_alist, p, 2);
2370 // Vim will take care of arranging the files added to the arglist
2371 // in windows or tabs; all we must do is to specify which layout to
2373 initialWindowLayout = layout;
2376 // When Vim is already open we resort to some trickery to open the
2377 // files with the specified layout.
2379 // TODO: Figure out a better way to handle this?
2380 if (openFiles && numFiles > 0) {
2381 BOOL oneWindowInTab = topframe ? YES
2382 : (topframe->fr_layout == FR_LEAF);
2383 BOOL bufChanged = NO;
2384 BOOL bufHasFilename = NO;
2386 bufChanged = curbufIsChanged();
2387 bufHasFilename = curbuf->b_ffname != NULL;
2390 // Temporarily disable flushing since the following code may
2391 // potentially cause multiple redraws.
2392 flushDisabled = YES;
2394 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2395 if (WIN_TABS == layout && !onlyOneTab) {
2396 // By going to the last tabpage we ensure that the new tabs
2397 // will appear last (if this call is left out, the taborder
2402 // Make sure we're in normal mode first.
2403 [self addInput:@"<C-\\><C-N>"];
2406 // With "split layout" we open a new tab before opening
2407 // multiple files if the current tab has more than one window
2408 // or if there is exactly one window but whose buffer has a
2409 // filename. (The :drop command ensures modified buffers get
2410 // their own window.)
2411 if ((WIN_HOR == layout || WIN_VER == layout) &&
2412 (!oneWindowInTab || bufHasFilename))
2413 [self addInput:@":tabnew<CR>"];
2415 // The files are opened by constructing a ":drop ..." command
2416 // and executing it.
2417 NSMutableString *cmd = (WIN_TABS == layout)
2418 ? [NSMutableString stringWithString:@":tab drop"]
2419 : [NSMutableString stringWithString:@":drop"];
2421 for (i = 0; i < numFiles; ++i) {
2422 NSString *file = [filenames objectAtIndex:i];
2423 file = [file stringByEscapingSpecialFilenameCharacters];
2424 [cmd appendString:@" "];
2425 [cmd appendString:file];
2428 // Temporarily clear 'suffixes' so that the files are opened in
2429 // the same order as they appear in the "filenames" array.
2430 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2432 [self addInput:cmd];
2434 // Split the view into multiple windows if requested.
2435 if (WIN_HOR == layout)
2436 [self addInput:@"|sall"];
2437 else if (WIN_VER == layout)
2438 [self addInput:@"|vert sall"];
2440 // Restore the old value of 'suffixes'.
2441 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2443 // When opening one file we try to reuse the current window,
2444 // but not if its buffer is modified or has a filename.
2445 // However, the 'arglist' layout always opens the file in the
2447 NSString *file = [[filenames lastObject]
2448 stringByEscapingSpecialFilenameCharacters];
2450 if (WIN_HOR == layout) {
2451 if (!(bufHasFilename || bufChanged))
2452 cmd = [NSString stringWithFormat:@":e %@", file];
2454 cmd = [NSString stringWithFormat:@":sp %@", file];
2455 } else if (WIN_VER == layout) {
2456 if (!(bufHasFilename || bufChanged))
2457 cmd = [NSString stringWithFormat:@":e %@", file];
2459 cmd = [NSString stringWithFormat:@":vsp %@", file];
2460 } else if (WIN_TABS == layout) {
2461 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2462 cmd = [NSString stringWithFormat:@":e %@", file];
2464 cmd = [NSString stringWithFormat:@":tabe %@", file];
2466 // (The :drop command will split if there is a modified
2468 cmd = [NSString stringWithFormat:@":drop %@", file];
2471 [self addInput:cmd];
2472 [self addInput:@"<CR>"];
2475 // Force screen redraw (does it have to be this complicated?).
2476 // (This code was taken from the end of gui_handle_drop().)
2477 update_screen(NOT_VALID);
2480 gui_update_cursor(FALSE, FALSE);
2487 if ([args objectForKey:@"remoteID"]) {
2488 // NOTE: We have to delay processing any ODB related arguments since
2489 // the file(s) may not be opened until the input buffer is processed.
2490 [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2495 NSString *lineString = [args objectForKey:@"cursorLine"];
2496 if (lineString && [lineString intValue] > 0) {
2497 NSString *columnString = [args objectForKey:@"cursorColumn"];
2498 if (!(columnString && [columnString intValue] > 0))
2499 columnString = @"1";
2501 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2502 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2503 [self addInput:cmd];
2506 NSString *rangeString = [args objectForKey:@"selectionRange"];
2508 // Build a command line string that will select the given range of
2509 // lines. If range.length == 0, then position the cursor on the given
2510 // line but do not select.
2511 NSRange range = NSRangeFromString(rangeString);
2513 if (range.length > 0) {
2514 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2515 NSMaxRange(range), range.location];
2517 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2521 [self addInput:cmd];
2524 NSString *searchText = [args objectForKey:@"searchText"];
2526 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@/e<CR>",
2531 - (BOOL)checkForModifiedBuffers
2534 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2535 if (bufIsChanged(buf)) {
2543 - (void)addInput:(NSString *)input
2545 // NOTE: This code is essentially identical to server_to_input_buf(),
2546 // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2547 char_u *string = [input vimStringSave];
2548 if (!string) return;
2550 /* Set 'cpoptions' the way we want it.
2551 * B set - backslashes are *not* treated specially
2552 * k set - keycodes are *not* reverse-engineered
2553 * < unset - <Key> sequences *are* interpreted
2554 * The last but one parameter of replace_termcodes() is TRUE so that the
2555 * <lt> sequence is recognised - needed for a real backslash.
2558 char_u *cpo_save = p_cpo;
2559 p_cpo = (char_u *)"Bk";
2560 char_u *str = replace_termcodes((char_u *)string, &ptr, FALSE, TRUE, FALSE);
2563 if (*ptr != NUL) /* trailing CTRL-V results in nothing */
2566 * Add the string to the input stream.
2567 * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2569 * First clear typed characters from the typeahead buffer, there could
2570 * be half a mapping there. Then append to the existing string, so
2571 * that multiple commands from a client are concatenated.
2573 if (typebuf.tb_maplen < typebuf.tb_len)
2574 del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2575 (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2577 /* Let input_available() know we inserted text in the typeahead
2579 typebuf_was_filled = TRUE;
2585 - (BOOL)unusedEditor
2587 BOOL oneWindowInTab = topframe ? YES
2588 : (topframe->fr_layout == FR_LEAF);
2589 BOOL bufChanged = NO;
2590 BOOL bufHasFilename = NO;
2592 bufChanged = curbufIsChanged();
2593 bufHasFilename = curbuf->b_ffname != NULL;
2596 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2598 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2601 - (void)redrawScreen
2603 // Force screen redraw (does it have to be this complicated?).
2604 redraw_all_later(CLEAR);
2605 update_screen(NOT_VALID);
2608 gui_update_cursor(FALSE, FALSE);
2610 // HACK! The cursor is not put back at the command line by the above
2611 // "redraw commands". The following test seems to do the trick though.
2612 if (State & CMDLINE)
2616 - (void)handleFindReplace:(NSDictionary *)args
2620 NSString *findString = [args objectForKey:@"find"];
2621 if (!findString) return;
2623 char_u *find = [findString vimStringSave];
2624 char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2625 int flags = [[args objectForKey:@"flags"] intValue];
2627 // NOTE: The flag 0x100 is used to indicate a backward search.
2628 gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2634 @end // MMBackend (Private)
2639 @implementation MMBackend (ClientServer)
2641 - (NSString *)connectionNameFromServerName:(NSString *)name
2643 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2645 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2649 - (NSConnection *)connectionForServerName:(NSString *)name
2651 // TODO: Try 'name%d' if 'name' fails.
2652 NSString *connName = [self connectionNameFromServerName:name];
2653 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2656 svrConn = [NSConnection connectionWithRegisteredName:connName
2658 // Try alternate server...
2659 if (!svrConn && alternateServerName) {
2660 //NSLog(@" trying to connect to alternate server: %@",
2661 // alternateServerName);
2662 connName = [self connectionNameFromServerName:alternateServerName];
2663 svrConn = [NSConnection connectionWithRegisteredName:connName
2667 // Try looking for alternate servers...
2669 //NSLog(@" looking for alternate servers...");
2670 NSString *alt = [self alternateServerNameForName:name];
2671 if (alt != alternateServerName) {
2672 //NSLog(@" found alternate server: %@", string);
2673 [alternateServerName release];
2674 alternateServerName = [alt copy];
2678 // Try alternate server again...
2679 if (!svrConn && alternateServerName) {
2680 //NSLog(@" trying to connect to alternate server: %@",
2681 // alternateServerName);
2682 connName = [self connectionNameFromServerName:alternateServerName];
2683 svrConn = [NSConnection connectionWithRegisteredName:connName
2688 [connectionNameDict setObject:svrConn forKey:connName];
2690 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2691 [[NSNotificationCenter defaultCenter] addObserver:self
2692 selector:@selector(serverConnectionDidDie:)
2693 name:NSConnectionDidDieNotification object:svrConn];
2700 - (NSConnection *)connectionForServerPort:(int)port
2703 NSEnumerator *e = [connectionNameDict objectEnumerator];
2705 while ((conn = [e nextObject])) {
2706 // HACK! Assume connection uses mach ports.
2707 if (port == [(NSMachPort*)[conn sendPort] machPort])
2714 - (void)serverConnectionDidDie:(NSNotification *)notification
2716 //NSLog(@"%s%@", _cmd, notification);
2718 NSConnection *svrConn = [notification object];
2720 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2721 [[NSNotificationCenter defaultCenter]
2723 name:NSConnectionDidDieNotification
2726 [connectionNameDict removeObjectsForKeys:
2727 [connectionNameDict allKeysForObject:svrConn]];
2729 // HACK! Assume connection uses mach ports.
2730 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2731 NSNumber *key = [NSNumber numberWithInt:port];
2733 [clientProxyDict removeObjectForKey:key];
2734 [serverReplyDict removeObjectForKey:key];
2737 - (void)addClient:(NSDistantObject *)client
2739 NSConnection *conn = [client connectionForProxy];
2740 // HACK! Assume connection uses mach ports.
2741 int port = [(NSMachPort*)[conn sendPort] machPort];
2742 NSNumber *key = [NSNumber numberWithInt:port];
2744 if (![clientProxyDict objectForKey:key]) {
2745 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2746 [clientProxyDict setObject:client forKey:key];
2749 // NOTE: 'clientWindow' is a global variable which is used by <client>
2750 clientWindow = port;
2753 - (NSString *)alternateServerNameForName:(NSString *)name
2755 if (!(name && [name length] > 0))
2758 // Only look for alternates if 'name' doesn't end in a digit.
2759 unichar lastChar = [name characterAtIndex:[name length]-1];
2760 if (lastChar >= '0' && lastChar <= '9')
2763 // Look for alternates among all current servers.
2764 NSArray *list = [self serverList];
2765 if (!(list && [list count] > 0))
2768 // Filter out servers starting with 'name' and ending with a number. The
2769 // (?i) pattern ensures that the match is case insensitive.
2770 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2771 NSPredicate *pred = [NSPredicate predicateWithFormat:
2772 @"SELF MATCHES %@", pat];
2773 list = [list filteredArrayUsingPredicate:pred];
2774 if ([list count] > 0) {
2775 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2776 return [list objectAtIndex:0];
2782 @end // MMBackend (ClientServer)
2787 @implementation NSString (MMServerNameCompare)
2788 - (NSComparisonResult)serverNameCompare:(NSString *)string
2790 return [self compare:string
2791 options:NSCaseInsensitiveSearch|NSNumericSearch];
2798 static int eventModifierFlagsToVimModMask(int modifierFlags)
2802 if (modifierFlags & NSShiftKeyMask)
2803 modMask |= MOD_MASK_SHIFT;
2804 if (modifierFlags & NSControlKeyMask)
2805 modMask |= MOD_MASK_CTRL;
2806 if (modifierFlags & NSAlternateKeyMask)
2807 modMask |= MOD_MASK_ALT;
2808 if (modifierFlags & NSCommandKeyMask)
2809 modMask |= MOD_MASK_CMD;
2814 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2818 if (modifierFlags & NSShiftKeyMask)
2819 modMask |= MOUSE_SHIFT;
2820 if (modifierFlags & NSControlKeyMask)
2821 modMask |= MOUSE_CTRL;
2822 if (modifierFlags & NSAlternateKeyMask)
2823 modMask |= MOUSE_ALT;
2828 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2830 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2832 return (buttonNumber >= 0 && buttonNumber < 3)
2833 ? mouseButton[buttonNumber] : -1;
2838 // This function is modeled after the VimToPython function found in if_python.c
2839 // NB This does a deep copy by value, it does not lookup references like the
2840 // VimToPython function does. This is because I didn't want to deal with the
2841 // retain cycles that this would create, and we can cover 99% of the use cases
2842 // by ignoring it. If we ever switch to using GC in MacVim then this
2843 // functionality can be implemented easily.
2844 static id vimToCocoa(typval_T * tv, int depth)
2850 // Avoid infinite recursion
2855 if (tv->v_type == VAR_STRING) {
2856 char_u * val = tv->vval.v_string;
2857 // val can be NULL if the string is empty
2859 result = [NSString string];
2862 val = CONVERT_TO_UTF8(val);
2864 result = [NSString stringWithUTF8String:(char*)val];
2866 CONVERT_TO_UTF8_FREE(val);
2869 } else if (tv->v_type == VAR_NUMBER) {
2870 // looks like sizeof(varnumber_T) is always <= sizeof(long)
2871 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2872 } else if (tv->v_type == VAR_LIST) {
2873 list_T * list = tv->vval.v_list;
2876 NSMutableArray * arr = result = [NSMutableArray array];
2879 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2880 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2881 [arr addObject:newObj];
2884 } else if (tv->v_type == VAR_DICT) {
2885 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2887 if (tv->vval.v_dict != NULL) {
2888 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2889 int todo = ht->ht_used;
2893 for (hi = ht->ht_array; todo > 0; ++hi) {
2894 if (!HASHITEM_EMPTY(hi)) {
2897 di = dict_lookup(hi);
2898 newObj = vimToCocoa(&di->di_tv, depth + 1);
2900 char_u * keyval = hi->hi_key;
2902 keyval = CONVERT_TO_UTF8(keyval);
2904 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2906 CONVERT_TO_UTF8_FREE(keyval);
2908 [dict setObject:newObj forKey:key];
2912 } else { // only func refs should fall into this category?
2920 // This function is modeled after eval_client_expr_to_string found in main.c
2921 // Returns nil if there was an error evaluating the expression, and writes a
2922 // message to errorStr.
2923 // TODO Get the error that occurred while evaluating the expression in vim
2925 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2928 char_u *s = (char_u*)[expr UTF8String];
2931 s = CONVERT_FROM_UTF8(s);
2934 int save_dbl = debug_break_level;
2935 int save_ro = redir_off;
2937 debug_break_level = -1;
2941 typval_T * tvres = eval_expr(s, NULL);
2943 debug_break_level = save_dbl;
2944 redir_off = save_ro;
2951 CONVERT_FROM_UTF8_FREE(s);
2956 gui_update_cursor(FALSE, FALSE);
2959 if (tvres == NULL) {
2961 *errstr = @"Expression evaluation failed.";
2964 id res = vimToCocoa(tvres, 1);
2969 *errstr = @"Conversion to cocoa values failed.";
2977 @implementation NSString (VimStrings)
2979 + (id)stringWithVimString:(char_u *)s
2981 // This method ensures a non-nil string is returned. If 's' cannot be
2982 // converted to a utf-8 string it is assumed to be latin-1. If conversion
2983 // still fails an empty NSString is returned.
2984 NSString *string = nil;
2987 s = CONVERT_TO_UTF8(s);
2989 string = [NSString stringWithUTF8String:(char*)s];
2991 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2993 string = [NSString stringWithCString:(char*)s
2994 encoding:NSISOLatin1StringEncoding];
2997 CONVERT_TO_UTF8_FREE(s);
3001 return string != nil ? string : [NSString string];
3004 - (char_u *)vimStringSave
3006 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
3009 s = CONVERT_FROM_UTF8(s);
3011 ret = vim_strsave(s);
3013 CONVERT_FROM_UTF8_FREE(s);
3019 @end // NSString (VimStrings)