1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
6 * Do ":help uganda" in Vim to read copying and usage conditions.
7 * Do ":help credits" in Vim to see a list of people who contributed.
8 * See README.txt for an overview of the Vim source code.
13 * MMBackend communicates with the frontend (MacVim). It maintains a queue of
14 * output which is flushed to the frontend under controlled circumstances (so
15 * as to maintain a steady framerate). Input from the frontend is also handled
18 * The frontend communicates with the backend via the MMBackendProtocol. In
19 * particular, input is sent to the backend via processInput:data: and Vim
20 * state can be queried from the frontend with evaluateExpression:.
22 * It is very important to realize that all state is held by the backend, the
23 * frontend must either ask for state [MMBackend evaluateExpression:] or wait
24 * for the backend to update [MMVimController processCommandQueue:].
26 * The client/server functionality of Vim is handled by the backend. It sets
27 * up a named NSConnection to which other Vim processes can connect.
34 // NOTE: Colors in MMBackend are stored as unsigned ints on the form 0xaarrggbb
35 // whereas colors in Vim are int without the alpha component. Also note that
36 // 'transp' is assumed to be a value between 0 and 100.
37 #define MM_COLOR(col) ((unsigned)( ((col)&0xffffff) | 0xff000000 ))
38 #define MM_COLOR_WITH_TRANSP(col,transp) \
39 ((unsigned)( ((col)&0xffffff) \
40 | ((((unsigned)((((100-(transp))*255)/100)+.5f))&0xff)<<24) ))
42 // Values for window layout (must match values in main.c).
43 #define WIN_HOR 1 // "-o" horizontally split windows
44 #define WIN_VER 2 // "-O" vertically split windows
45 #define WIN_TABS 3 // "-p" windows on tab pages
47 static unsigned MMServerMax = 1000;
49 // TODO: Move to separate file.
50 static int eventModifierFlagsToVimModMask(int modifierFlags);
51 static int eventModifierFlagsToVimMouseModMask(int modifierFlags);
52 static int eventButtonNumberToVimMouseButton(int buttonNumber);
54 // Before exiting process, sleep for this many microseconds. This is to allow
55 // any distributed object messages in transit to be received by MacVim before
56 // the process dies (otherwise an error message is logged by Cocoa). Note that
57 // this delay is only necessary if an NSConnection to MacVim has been
59 static useconds_t MMExitProcessDelay = 300000;
62 vimmenu_T *menu_for_descriptor(NSArray *desc);
64 static id evalExprCocoa(NSString * expr, NSString ** errstr);
73 static NSString *MMSymlinkWarningString =
74 @"\n\n\tMost likely this is because you have symlinked directly to\n"
75 "\tthe Vim binary, which Cocoa does not allow. Please use an\n"
76 "\talias or the mvim shell script instead. If you have not used\n"
77 "\ta symlink, then your MacVim.app bundle is incomplete.\n\n";
81 @interface NSString (MMServerNameCompare)
82 - (NSComparisonResult)serverNameCompare:(NSString *)string;
88 @interface MMBackend (Private)
89 - (void)waitForDialogReturn;
90 - (void)insertVimStateMessage;
91 - (void)processInputQueue;
92 - (void)handleInputEvent:(int)msgid data:(NSData *)data;
93 + (NSDictionary *)specialKeys;
94 - (void)handleInsertText:(NSString *)text;
95 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods;
96 - (void)queueMessage:(int)msgid data:(NSData *)data;
97 - (void)connectionDidDie:(NSNotification *)notification;
98 - (void)blinkTimerFired:(NSTimer *)timer;
99 - (void)focusChange:(BOOL)on;
100 - (void)handleToggleToolbar;
101 - (void)handleScrollbarEvent:(NSData *)data;
102 - (void)handleSetFont:(NSData *)data;
103 - (void)handleDropFiles:(NSData *)data;
104 - (void)handleDropString:(NSData *)data;
105 - (void)startOdbEditWithArguments:(NSDictionary *)args;
106 - (void)handleXcodeMod:(NSData *)data;
107 - (void)handleOpenWithArguments:(NSDictionary *)args;
108 - (BOOL)checkForModifiedBuffers;
109 - (void)addInput:(NSString *)input;
110 - (BOOL)unusedEditor;
111 - (void)redrawScreen;
112 - (void)handleFindReplace:(NSDictionary *)args;
117 @interface MMBackend (ClientServer)
118 - (NSString *)connectionNameFromServerName:(NSString *)name;
119 - (NSConnection *)connectionForServerName:(NSString *)name;
120 - (NSConnection *)connectionForServerPort:(int)port;
121 - (void)serverConnectionDidDie:(NSNotification *)notification;
122 - (void)addClient:(NSDistantObject *)client;
123 - (NSString *)alternateServerNameForName:(NSString *)name;
128 @implementation MMBackend
130 + (MMBackend *)sharedInstance
132 static MMBackend *singleton = nil;
133 return singleton ? singleton : (singleton = [MMBackend new]);
139 if (!self) return nil;
141 fontContainerRef = loadFonts();
143 outputQueue = [[NSMutableArray alloc] init];
144 inputQueue = [[NSMutableArray alloc] init];
145 drawData = [[NSMutableData alloc] initWithCapacity:1024];
146 connectionNameDict = [[NSMutableDictionary alloc] init];
147 clientProxyDict = [[NSMutableDictionary alloc] init];
148 serverReplyDict = [[NSMutableDictionary alloc] init];
150 NSBundle *mainBundle = [NSBundle mainBundle];
151 NSString *path = [mainBundle pathForResource:@"Colors" ofType:@"plist"];
153 colorDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
155 path = [mainBundle pathForResource:@"SystemColors" ofType:@"plist"];
157 sysColorDict = [[NSDictionary dictionaryWithContentsOfFile:path]
160 path = [mainBundle pathForResource:@"Actions" ofType:@"plist"];
162 actionDict = [[NSDictionary dictionaryWithContentsOfFile:path] retain];
164 if (!(colorDict && sysColorDict && actionDict))
165 NSLog(@"ERROR: Failed to load dictionaries.%@",
166 MMSymlinkWarningString);
173 //NSLog(@"%@ %s", [self className], _cmd);
174 [[NSNotificationCenter defaultCenter] removeObserver:self];
176 [oldWideFont release]; oldWideFont = nil;
177 [blinkTimer release]; blinkTimer = nil;
178 [alternateServerName release]; alternateServerName = nil;
179 [serverReplyDict release]; serverReplyDict = nil;
180 [clientProxyDict release]; clientProxyDict = nil;
181 [connectionNameDict release]; connectionNameDict = nil;
182 [inputQueue release]; inputQueue = nil;
183 [outputQueue release]; outputQueue = nil;
184 [drawData release]; drawData = nil;
185 [frontendProxy release]; frontendProxy = nil;
186 [connection release]; connection = nil;
187 [actionDict release]; actionDict = nil;
188 [sysColorDict release]; sysColorDict = nil;
189 [colorDict release]; colorDict = nil;
194 - (void)setBackgroundColor:(int)color
196 backgroundColor = MM_COLOR_WITH_TRANSP(color,p_transp);
199 - (void)setForegroundColor:(int)color
201 foregroundColor = MM_COLOR(color);
204 - (void)setSpecialColor:(int)color
206 specialColor = MM_COLOR(color);
209 - (void)setDefaultColorsBackground:(int)bg foreground:(int)fg
211 defaultBackgroundColor = MM_COLOR_WITH_TRANSP(bg,p_transp);
212 defaultForegroundColor = MM_COLOR(fg);
214 NSMutableData *data = [NSMutableData data];
216 [data appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
217 [data appendBytes:&defaultForegroundColor length:sizeof(unsigned)];
219 [self queueMessage:SetDefaultColorsMsgID data:data];
222 - (NSConnection *)connection
225 // NOTE! If the name of the connection changes here it must also be
226 // updated in MMAppController.m.
227 NSString *name = [NSString stringWithFormat:@"%@-connection",
228 [[NSBundle mainBundle] bundlePath]];
230 connection = [NSConnection connectionWithRegisteredName:name host:nil];
234 // NOTE: 'connection' may be nil here.
238 - (NSDictionary *)actionDict
243 - (int)initialWindowLayout
245 return initialWindowLayout;
248 - (void)queueMessage:(int)msgid properties:(NSDictionary *)props
250 [self queueMessage:msgid data:[props dictionaryAsData]];
255 if (![self connection]) {
257 // This is a preloaded process and as such should not cause the
258 // MacVim to be opened. We probably got here as a result of the
259 // user quitting MacVim while the process was preloading, so exit
261 // (Don't use mch_exit() since it assumes the process has properly
266 NSBundle *mainBundle = [NSBundle mainBundle];
271 // Launch MacVim using Launch Services (NSWorkspace would be nicer, but
272 // the API to pass Apple Event parameters is broken on 10.4).
273 NSString *path = [mainBundle bundlePath];
274 status = FSPathMakeRef((const UInt8 *)[path UTF8String], &ref, NULL);
275 if (noErr == status) {
276 // Pass parameter to the 'Open' Apple Event that tells MacVim not
277 // to open an untitled window.
278 NSAppleEventDescriptor *desc =
279 [NSAppleEventDescriptor recordDescriptor];
280 [desc setParamDescriptor:
281 [NSAppleEventDescriptor descriptorWithBoolean:NO]
282 forKeyword:keyMMUntitledWindow];
284 LSLaunchFSRefSpec spec = { &ref, 0, NULL, [desc aeDesc],
285 kLSLaunchDefaults, NULL };
286 status = LSOpenFromRefSpec(&spec, NULL);
289 if (noErr != status) {
290 NSLog(@"ERROR: Failed to launch MacVim (path=%@).%@",
291 path, MMSymlinkWarningString);
295 // Launch MacVim using NSTask. For some reason the above code using
296 // Launch Services sometimes fails on LSOpenFromRefSpec() (when it
297 // fails, the dock icon starts bouncing and never stops). It seems
298 // like rebuilding the Launch Services database takes care of this
299 // problem, but the NSTask way seems more stable so stick with it.
301 // NOTE! Using NSTask to launch the GUI has the negative side-effect
302 // that the GUI won't be activated (or raised) so there is a hack in
303 // MMAppController which raises the app when a new window is opened.
304 NSMutableArray *args = [NSMutableArray arrayWithObjects:
305 [NSString stringWithFormat:@"-%@", MMNoWindowKey], @"yes", nil];
306 NSString *exeName = [[mainBundle infoDictionary]
307 objectForKey:@"CFBundleExecutable"];
308 NSString *path = [mainBundle pathForAuxiliaryExecutable:exeName];
310 NSLog(@"ERROR: Could not find MacVim executable in bundle.%@",
311 MMSymlinkWarningString);
315 [NSTask launchedTaskWithLaunchPath:path arguments:args];
318 // HACK! Poll the mach bootstrap server until it returns a valid
319 // connection to detect that MacVim has finished launching. Also set a
320 // time-out date so that we don't get stuck doing this forever.
321 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:10];
322 while (![self connection] &&
323 NSOrderedDescending == [timeOutDate compare:[NSDate date]])
324 [[NSRunLoop currentRunLoop]
325 runMode:NSDefaultRunLoopMode
326 beforeDate:[NSDate dateWithTimeIntervalSinceNow:.1]];
328 // NOTE: [self connection] will set 'connection' as a side-effect.
330 NSLog(@"WARNING: Timed-out waiting for GUI to launch.");
337 [[NSNotificationCenter defaultCenter] addObserver:self
338 selector:@selector(connectionDidDie:)
339 name:NSConnectionDidDieNotification object:connection];
341 id proxy = [connection rootProxy];
342 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
344 int pid = [[NSProcessInfo processInfo] processIdentifier];
346 frontendProxy = [proxy connectBackend:self pid:pid];
348 [frontendProxy retain];
349 [frontendProxy setProtocolForProxy:@protocol(MMAppProtocol)];
353 @catch (NSException *e) {
354 NSLog(@"Exception caught when trying to connect backend: \"%@\"", e);
360 - (BOOL)openGUIWindow
362 [self queueMessage:OpenWindowMsgID data:nil];
368 int type = ClearAllDrawType;
370 // Any draw commands in queue are effectively obsolete since this clearAll
371 // will negate any effect they have, therefore we may as well clear the
373 [drawData setLength:0];
375 [drawData appendBytes:&type length:sizeof(int)];
378 - (void)clearBlockFromRow:(int)row1 column:(int)col1
379 toRow:(int)row2 column:(int)col2
381 int type = ClearBlockDrawType;
383 [drawData appendBytes:&type length:sizeof(int)];
385 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
386 [drawData appendBytes:&row1 length:sizeof(int)];
387 [drawData appendBytes:&col1 length:sizeof(int)];
388 [drawData appendBytes:&row2 length:sizeof(int)];
389 [drawData appendBytes:&col2 length:sizeof(int)];
392 - (void)deleteLinesFromRow:(int)row count:(int)count
393 scrollBottom:(int)bottom left:(int)left right:(int)right
395 int type = DeleteLinesDrawType;
397 [drawData appendBytes:&type length:sizeof(int)];
399 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
400 [drawData appendBytes:&row length:sizeof(int)];
401 [drawData appendBytes:&count length:sizeof(int)];
402 [drawData appendBytes:&bottom length:sizeof(int)];
403 [drawData appendBytes:&left length:sizeof(int)];
404 [drawData appendBytes:&right length:sizeof(int)];
407 - (void)drawString:(char*)s length:(int)len row:(int)row column:(int)col
408 cells:(int)cells flags:(int)flags
410 if (len <= 0 || cells <= 0) return;
412 int type = DrawStringDrawType;
414 [drawData appendBytes:&type length:sizeof(int)];
416 [drawData appendBytes:&backgroundColor length:sizeof(unsigned)];
417 [drawData appendBytes:&foregroundColor length:sizeof(unsigned)];
418 [drawData appendBytes:&specialColor length:sizeof(unsigned)];
419 [drawData appendBytes:&row length:sizeof(int)];
420 [drawData appendBytes:&col length:sizeof(int)];
421 [drawData appendBytes:&cells length:sizeof(int)];
422 [drawData appendBytes:&flags length:sizeof(int)];
423 [drawData appendBytes:&len length:sizeof(int)];
424 [drawData appendBytes:s length:len];
427 - (void)insertLinesFromRow:(int)row count:(int)count
428 scrollBottom:(int)bottom left:(int)left right:(int)right
430 int type = InsertLinesDrawType;
432 [drawData appendBytes:&type length:sizeof(int)];
434 [drawData appendBytes:&defaultBackgroundColor length:sizeof(unsigned)];
435 [drawData appendBytes:&row length:sizeof(int)];
436 [drawData appendBytes:&count length:sizeof(int)];
437 [drawData appendBytes:&bottom length:sizeof(int)];
438 [drawData appendBytes:&left length:sizeof(int)];
439 [drawData appendBytes:&right length:sizeof(int)];
442 - (void)drawCursorAtRow:(int)row column:(int)col shape:(int)shape
443 fraction:(int)percent color:(int)color
445 int type = DrawCursorDrawType;
446 unsigned uc = MM_COLOR(color);
448 [drawData appendBytes:&type length:sizeof(int)];
450 [drawData appendBytes:&uc length:sizeof(unsigned)];
451 [drawData appendBytes:&row length:sizeof(int)];
452 [drawData appendBytes:&col length:sizeof(int)];
453 [drawData appendBytes:&shape length:sizeof(int)];
454 [drawData appendBytes:&percent length:sizeof(int)];
457 - (void)drawInvertedRectAtRow:(int)row column:(int)col numRows:(int)nr
458 numColumns:(int)nc invert:(int)invert
460 int type = DrawInvertedRectDrawType;
461 [drawData appendBytes:&type length:sizeof(int)];
463 [drawData appendBytes:&row length:sizeof(int)];
464 [drawData appendBytes:&col length:sizeof(int)];
465 [drawData appendBytes:&nr length:sizeof(int)];
466 [drawData appendBytes:&nc length:sizeof(int)];
467 [drawData appendBytes:&invert length:sizeof(int)];
472 // Keep running the run-loop until there is no more input to process.
473 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, 0, true)
474 == kCFRunLoopRunHandledSource)
478 - (void)flushQueue:(BOOL)force
480 // NOTE: This variable allows for better control over when the queue is
481 // flushed. It can be set to YES at the beginning of a sequence of calls
482 // that may potentially add items to the queue, and then restored back to
484 if (flushDisabled) return;
486 if ([drawData length] > 0) {
487 // HACK! Detect changes to 'guifontwide'.
488 if (gui.wide_font != (GuiFont)oldWideFont) {
489 [oldWideFont release];
490 oldWideFont = [(NSFont*)gui.wide_font retain];
491 [self setWideFont:oldWideFont];
494 int type = SetCursorPosDrawType;
495 [drawData appendBytes:&type length:sizeof(type)];
496 [drawData appendBytes:&gui.row length:sizeof(gui.row)];
497 [drawData appendBytes:&gui.col length:sizeof(gui.col)];
499 [self queueMessage:BatchDrawMsgID data:[drawData copy]];
500 [drawData setLength:0];
503 if ([outputQueue count] > 0) {
504 [self insertVimStateMessage];
507 [frontendProxy processCommandQueue:outputQueue];
509 @catch (NSException *e) {
510 NSLog(@"Exception caught when processing command queue: \"%@\"", e);
511 NSLog(@"outputQueue(len:%d)=%@", [outputQueue count]/2,
513 if (![connection isValid]) {
514 NSLog(@"WARNING! Connection is invalid, exit now!");
515 NSLog(@"waitForAck=%d got_int=%d isTerminating=%d",
516 waitForAck, got_int, isTerminating);
521 [outputQueue removeAllObjects];
525 - (BOOL)waitForInput:(int)milliseconds
527 // Return NO if we timed out waiting for input, otherwise return YES.
528 BOOL inputReceived = NO;
530 // Only start the run loop if the input queue is empty, otherwise process
531 // the input first so that the input on queue isn't delayed.
532 if ([inputQueue count]) {
535 // Wait for the specified amount of time, unless 'milliseconds' is
536 // negative in which case we wait "forever" (1e6 seconds translates to
537 // approximately 11 days).
538 CFTimeInterval dt = (milliseconds >= 0 ? .001*milliseconds : 1e6);
540 while (CFRunLoopRunInMode(kCFRunLoopDefaultMode, dt, true)
541 == kCFRunLoopRunHandledSource) {
542 // In order to ensure that all input on the run-loop has been
543 // processed we set the timeout to 0 and keep processing until the
544 // run-loop times out.
550 // The above calls may have placed messages on the input queue so process
551 // it now. This call may enter a blocking loop.
552 if ([inputQueue count] > 0)
553 [self processInputQueue];
555 return inputReceived;
560 // NOTE: This is called if mch_exit() is called. Since we assume here that
561 // the process has started properly, be sure to use exit() instead of
562 // mch_exit() to prematurely terminate a process.
564 // To notify MacVim that this Vim process is exiting we could simply
565 // invalidate the connection and it would automatically receive a
566 // connectionDidDie: notification. However, this notification seems to
567 // take up to 300 ms to arrive which is quite a noticeable delay. Instead
568 // we immediately send a message to MacVim asking it to close the window
569 // belonging to this process, and then we invalidate the connection (in
570 // case the message got lost).
572 // Make sure no connectionDidDie: notification is received now that we are
574 [[NSNotificationCenter defaultCenter] removeObserver:self];
576 if ([connection isValid]) {
578 // Flush the entire queue in case a VimLeave autocommand added
579 // something to the queue.
580 [self queueMessage:CloseWindowMsgID data:nil];
581 [frontendProxy processCommandQueue:outputQueue];
583 @catch (NSException *e) {
584 NSLog(@"Exception caught when sending CloseWindowMsgID: \"%@\"", e);
587 [connection invalidate];
590 #ifdef MAC_CLIENTSERVER
591 // The default connection is used for the client/server code.
592 [[NSConnection defaultConnection] setRootObject:nil];
593 [[NSConnection defaultConnection] invalidate];
596 if (fontContainerRef) {
597 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
598 fontContainerRef = 0;
601 usleep(MMExitProcessDelay);
604 - (void)selectTab:(int)index
606 //NSLog(@"%s%d", _cmd, index);
609 NSData *data = [NSData dataWithBytes:&index length:sizeof(int)];
610 [self queueMessage:SelectTabMsgID data:data];
615 //NSLog(@"%s", _cmd);
617 NSMutableData *data = [NSMutableData data];
619 int idx = tabpage_index(curtab) - 1;
620 [data appendBytes:&idx length:sizeof(int)];
623 for (tp = first_tabpage; tp != NULL; tp = tp->tp_next) {
624 // Count the number of windows in the tabpage.
625 //win_T *wp = tp->tp_firstwin;
627 //for (wincount = 0; wp != NULL; wp = wp->w_next, ++wincount);
628 //[data appendBytes:&wincount length:sizeof(int)];
630 int tabProp = MMTabInfoCount;
631 [data appendBytes:&tabProp length:sizeof(int)];
632 for (tabProp = MMTabLabel; tabProp < MMTabInfoCount; ++tabProp) {
633 // This function puts the label of the tab in the global 'NameBuff'.
634 get_tabline_label(tp, (tabProp == MMTabToolTip));
635 NSString *s = [NSString stringWithVimString:NameBuff];
636 int len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
640 [data appendBytes:&len length:sizeof(int)];
642 [data appendBytes:[s UTF8String] length:len];
646 [self queueMessage:UpdateTabBarMsgID data:data];
649 - (BOOL)tabBarVisible
651 return tabBarVisible;
654 - (void)showTabBar:(BOOL)enable
656 tabBarVisible = enable;
658 int msgid = enable ? ShowTabBarMsgID : HideTabBarMsgID;
659 [self queueMessage:msgid data:nil];
662 - (void)setRows:(int)rows columns:(int)cols
664 //NSLog(@"[VimTask] setRows:%d columns:%d", rows, cols);
666 int dim[] = { rows, cols };
667 NSData *data = [NSData dataWithBytes:&dim length:2*sizeof(int)];
669 [self queueMessage:SetTextDimensionsMsgID data:data];
672 - (void)setWindowTitle:(char *)title
674 NSMutableData *data = [NSMutableData data];
675 int len = strlen(title);
676 if (len <= 0) return;
678 [data appendBytes:&len length:sizeof(int)];
679 [data appendBytes:title length:len];
681 [self queueMessage:SetWindowTitleMsgID data:data];
684 - (void)setDocumentFilename:(char *)filename
686 NSMutableData *data = [NSMutableData data];
687 int len = filename ? strlen(filename) : 0;
689 [data appendBytes:&len length:sizeof(int)];
691 [data appendBytes:filename length:len];
693 [self queueMessage:SetDocumentFilenameMsgID data:data];
696 - (char *)browseForFileWithAttributes:(NSDictionary *)attr
701 [frontendProxy showSavePanelWithAttributes:attr];
703 [self waitForDialogReturn];
705 if (dialogReturn && [dialogReturn isKindOfClass:[NSString class]])
706 s = [dialogReturn vimStringSave];
708 [dialogReturn release]; dialogReturn = nil;
710 @catch (NSException *e) {
711 NSLog(@"Exception caught when showing save panel: \"%@\"", e);
717 - (oneway void)setDialogReturn:(in bycopy id)obj
719 // NOTE: This is called by
720 // - [MMVimController panelDidEnd:::], and
721 // - [MMVimController alertDidEnd:::],
722 // to indicate that a save/open panel or alert has finished.
724 // We want to distinguish between "no dialog return yet" and "dialog
725 // returned nothing". The former can be tested with dialogReturn == nil,
726 // the latter with dialogReturn == [NSNull null].
727 if (!obj) obj = [NSNull null];
729 if (obj != dialogReturn) {
730 [dialogReturn release];
731 dialogReturn = [obj retain];
735 - (int)showDialogWithAttributes:(NSDictionary *)attr textField:(char *)txtfield
740 [frontendProxy presentDialogWithAttributes:attr];
742 [self waitForDialogReturn];
744 if (dialogReturn && [dialogReturn isKindOfClass:[NSArray class]]
745 && [dialogReturn count]) {
746 retval = [[dialogReturn objectAtIndex:0] intValue];
747 if (txtfield && [dialogReturn count] > 1) {
748 NSString *retString = [dialogReturn objectAtIndex:1];
749 char_u *ret = (char_u*)[retString UTF8String];
751 ret = CONVERT_FROM_UTF8(ret);
753 vim_strncpy((char_u*)txtfield, ret, IOSIZE - 1);
755 CONVERT_FROM_UTF8_FREE(ret);
760 [dialogReturn release]; dialogReturn = nil;
762 @catch (NSException *e) {
763 NSLog(@"Exception caught while showing alert dialog: \"%@\"", e);
769 - (void)showToolbar:(int)enable flags:(int)flags
771 NSMutableData *data = [NSMutableData data];
773 [data appendBytes:&enable length:sizeof(int)];
774 [data appendBytes:&flags length:sizeof(int)];
776 [self queueMessage:ShowToolbarMsgID data:data];
779 - (void)createScrollbarWithIdentifier:(long)ident type:(int)type
781 NSMutableData *data = [NSMutableData data];
783 [data appendBytes:&ident length:sizeof(long)];
784 [data appendBytes:&type length:sizeof(int)];
786 [self queueMessage:CreateScrollbarMsgID data:data];
789 - (void)destroyScrollbarWithIdentifier:(long)ident
791 NSMutableData *data = [NSMutableData data];
792 [data appendBytes:&ident length:sizeof(long)];
794 [self queueMessage:DestroyScrollbarMsgID data:data];
797 - (void)showScrollbarWithIdentifier:(long)ident state:(int)visible
799 NSMutableData *data = [NSMutableData data];
801 [data appendBytes:&ident length:sizeof(long)];
802 [data appendBytes:&visible length:sizeof(int)];
804 [self queueMessage:ShowScrollbarMsgID data:data];
807 - (void)setScrollbarPosition:(int)pos length:(int)len identifier:(long)ident
809 NSMutableData *data = [NSMutableData data];
811 [data appendBytes:&ident length:sizeof(long)];
812 [data appendBytes:&pos length:sizeof(int)];
813 [data appendBytes:&len length:sizeof(int)];
815 [self queueMessage:SetScrollbarPositionMsgID data:data];
818 - (void)setScrollbarThumbValue:(long)val size:(long)size max:(long)max
819 identifier:(long)ident
821 float fval = max-size+1 > 0 ? (float)val/(max-size+1) : 0;
822 float prop = (float)size/(max+1);
823 if (fval < 0) fval = 0;
824 else if (fval > 1.0f) fval = 1.0f;
825 if (prop < 0) prop = 0;
826 else if (prop > 1.0f) prop = 1.0f;
828 NSMutableData *data = [NSMutableData data];
830 [data appendBytes:&ident length:sizeof(long)];
831 [data appendBytes:&fval length:sizeof(float)];
832 [data appendBytes:&prop length:sizeof(float)];
834 [self queueMessage:SetScrollbarThumbMsgID data:data];
837 - (void)setFont:(NSFont *)font
839 NSString *fontName = [font displayName];
840 float size = [font pointSize];
841 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
843 NSMutableData *data = [NSMutableData data];
845 [data appendBytes:&size length:sizeof(float)];
846 [data appendBytes:&len length:sizeof(int)];
847 [data appendBytes:[fontName UTF8String] length:len];
849 [self queueMessage:SetFontMsgID data:data];
853 - (void)setWideFont:(NSFont *)font
855 NSString *fontName = [font displayName];
856 float size = [font pointSize];
857 int len = [fontName lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
858 NSMutableData *data = [NSMutableData data];
860 [data appendBytes:&size length:sizeof(float)];
861 [data appendBytes:&len length:sizeof(int)];
863 [data appendBytes:[fontName UTF8String] length:len];
865 [self queueMessage:SetWideFontMsgID data:data];
868 - (void)executeActionWithName:(NSString *)name
870 int len = [name lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
873 NSMutableData *data = [NSMutableData data];
875 [data appendBytes:&len length:sizeof(int)];
876 [data appendBytes:[name UTF8String] length:len];
878 [self queueMessage:ExecuteActionMsgID data:data];
882 - (void)setMouseShape:(int)shape
884 NSMutableData *data = [NSMutableData data];
885 [data appendBytes:&shape length:sizeof(int)];
886 [self queueMessage:SetMouseShapeMsgID data:data];
889 - (void)setBlinkWait:(int)wait on:(int)on off:(int)off
891 // Vim specifies times in milliseconds, whereas Cocoa wants them in
893 blinkWaitInterval = .001f*wait;
894 blinkOnInterval = .001f*on;
895 blinkOffInterval = .001f*off;
901 [blinkTimer invalidate];
902 [blinkTimer release];
906 if (blinkWaitInterval > 0 && blinkOnInterval > 0 && blinkOffInterval > 0
908 blinkState = MMBlinkStateOn;
910 [[NSTimer scheduledTimerWithTimeInterval:blinkWaitInterval
912 selector:@selector(blinkTimerFired:)
913 userInfo:nil repeats:NO] retain];
914 gui_update_cursor(TRUE, FALSE);
915 [self flushQueue:YES];
921 if (MMBlinkStateOff == blinkState) {
922 gui_update_cursor(TRUE, FALSE);
923 [self flushQueue:YES];
926 blinkState = MMBlinkStateNone;
929 - (void)adjustLinespace:(int)linespace
931 NSMutableData *data = [NSMutableData data];
932 [data appendBytes:&linespace length:sizeof(int)];
933 [self queueMessage:AdjustLinespaceMsgID data:data];
938 [self queueMessage:ActivateMsgID data:nil];
941 - (void)setPreEditRow:(int)row column:(int)col
943 NSMutableData *data = [NSMutableData data];
944 [data appendBytes:&row length:sizeof(int)];
945 [data appendBytes:&col length:sizeof(int)];
946 [self queueMessage:SetPreEditPositionMsgID data:data];
949 - (int)lookupColorWithKey:(NSString *)key
951 if (!(key && [key length] > 0))
954 NSString *stripKey = [[[[key lowercaseString]
955 stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]
956 componentsSeparatedByString:@" "]
957 componentsJoinedByString:@""];
959 if (stripKey && [stripKey length] > 0) {
960 // First of all try to lookup key in the color dictionary; note that
961 // all keys in this dictionary are lowercase with no whitespace.
962 id obj = [colorDict objectForKey:stripKey];
963 if (obj) return [obj intValue];
965 // The key was not in the dictionary; is it perhaps of the form
967 if ([stripKey length] > 1 && [stripKey characterAtIndex:0] == '#') {
968 NSScanner *scanner = [NSScanner scannerWithString:stripKey];
969 [scanner setScanLocation:1];
971 if ([scanner scanHexInt:&hex]) {
976 // As a last resort, check if it is one of the system defined colors.
977 // The keys in this dictionary are also lowercase with no whitespace.
978 obj = [sysColorDict objectForKey:stripKey];
980 NSColor *col = [NSColor performSelector:NSSelectorFromString(obj)];
983 col = [col colorUsingColorSpaceName:NSCalibratedRGBColorSpace];
984 [col getRed:&r green:&g blue:&b alpha:&a];
985 return (((int)(r*255+.5f) & 0xff) << 16)
986 + (((int)(g*255+.5f) & 0xff) << 8)
987 + ((int)(b*255+.5f) & 0xff);
992 //NSLog(@"WARNING: No color with key %@ found.", stripKey);
996 - (BOOL)hasSpecialKeyWithValue:(NSString *)value
998 NSEnumerator *e = [[MMBackend specialKeys] objectEnumerator];
1001 while ((obj = [e nextObject])) {
1002 if ([value isEqual:obj])
1009 - (void)enterFullscreen:(int)fuoptions background:(int)bg
1011 NSMutableData *data = [NSMutableData data];
1012 [data appendBytes:&fuoptions length:sizeof(int)];
1014 [data appendBytes:&bg length:sizeof(int)];
1015 [self queueMessage:EnterFullscreenMsgID data:data];
1018 - (void)leaveFullscreen
1020 [self queueMessage:LeaveFullscreenMsgID data:nil];
1023 - (void)setFullscreenBackgroundColor:(int)color
1025 NSMutableData *data = [NSMutableData data];
1026 color = MM_COLOR(color);
1027 [data appendBytes:&color length:sizeof(int)];
1029 [self queueMessage:SetFullscreenColorMsgID data:data];
1032 - (void)setAntialias:(BOOL)antialias
1034 int msgid = antialias ? EnableAntialiasMsgID : DisableAntialiasMsgID;
1036 [self queueMessage:msgid data:nil];
1039 - (void)updateModifiedFlag
1041 // Notify MacVim if _any_ buffer has changed from unmodified to modified or
1043 int msgid = [self checkForModifiedBuffers]
1044 ? BuffersModifiedMsgID : BuffersNotModifiedMsgID;
1046 [self queueMessage:msgid data:nil];
1049 - (oneway void)processInput:(int)msgid data:(in bycopy NSData *)data
1051 // Look for Cmd-. and Ctrl-C immediately instead of waiting until the input
1052 // queue is processed since that only happens in waitForInput: (and Vim
1053 // regularly checks for Ctrl-C in between waiting for input).
1054 // Similarly, TerminateNowMsgID must be checked immediately otherwise code
1055 // which waits on the run loop will fail to detect this message (e.g. in
1056 // waitForConnectionAcknowledgement).
1058 BOOL shouldClearQueue = NO;
1059 if (InterruptMsgID == msgid) {
1060 shouldClearQueue = YES;
1062 } else if (InsertTextMsgID == msgid && data != nil) {
1063 const void *bytes = [data bytes];
1064 bytes += sizeof(int);
1065 int len = *((int*)bytes); bytes += sizeof(int);
1067 char_u *str = (char_u*)bytes;
1068 if ((str[0] == Ctrl_C && ctrl_c_interrupts) ||
1069 (str[0] == intr_char && intr_char != Ctrl_C)) {
1070 shouldClearQueue = YES;
1074 } else if (TerminateNowMsgID == msgid) {
1075 shouldClearQueue = YES;
1076 isTerminating = YES;
1079 if (shouldClearQueue) {
1080 [inputQueue removeAllObjects];
1084 // Remove all previous instances of this message from the input queue, else
1085 // the input queue may fill up as a result of Vim not being able to keep up
1086 // with the speed at which new messages are received.
1087 // Keyboard input is never dropped, unless the input represents and
1088 // auto-repeated key.
1090 BOOL isKeyRepeat = NO;
1091 BOOL isKeyboardInput = NO;
1093 if (data && (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1094 CmdKeyMsgID == msgid)) {
1095 isKeyboardInput = YES;
1097 // The lowest bit of the first int is set if this key is a repeat.
1098 int flags = *((int*)[data bytes]);
1103 // Keyboard input is not removed from the queue; repeats are ignored if
1104 // there already is keyboard input on the input queue.
1105 if (isKeyRepeat || !isKeyboardInput) {
1106 int i, count = [inputQueue count];
1107 for (i = 1; i < count; i+=2) {
1108 if ([[inputQueue objectAtIndex:i-1] intValue] == msgid) {
1112 [inputQueue removeObjectAtIndex:i];
1113 [inputQueue removeObjectAtIndex:i-1];
1119 [inputQueue addObject:[NSNumber numberWithInt:msgid]];
1120 [inputQueue addObject:(data ? (id)data : [NSNull null])];
1123 - (oneway void)processInputAndData:(in bycopy NSArray *)messages
1125 // This is just a convenience method that allows the frontend to delay
1126 // sending messages.
1127 int i, count = [messages count];
1128 for (i = 1; i < count; i+=2)
1129 [self processInput:[[messages objectAtIndex:i-1] intValue]
1130 data:[messages objectAtIndex:i]];
1133 - (id)evaluateExpressionCocoa:(in bycopy NSString *)expr
1134 errorString:(out bycopy NSString **)errstr
1136 return evalExprCocoa(expr, errstr);
1140 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1142 NSString *eval = nil;
1143 char_u *s = (char_u*)[expr UTF8String];
1146 s = CONVERT_FROM_UTF8(s);
1149 char_u *res = eval_client_expr_to_string(s);
1152 CONVERT_FROM_UTF8_FREE(s);
1158 s = CONVERT_TO_UTF8(s);
1160 eval = [NSString stringWithUTF8String:(char*)s];
1162 CONVERT_TO_UTF8_FREE(s);
1170 - (BOOL)starRegisterToPasteboard:(byref NSPasteboard *)pboard
1172 // TODO: This method should share code with clip_mch_request_selection().
1174 if (VIsual_active && (State & NORMAL) && clip_star.available) {
1175 // If there is no pasteboard, return YES to indicate that there is text
1180 clip_copy_selection();
1182 // Get the text to put on the pasteboard.
1183 long_u llen = 0; char_u *str = 0;
1184 int type = clip_convert_selection(&str, &llen, &clip_star);
1188 // TODO: Avoid overflow.
1189 int len = (int)llen;
1191 if (output_conv.vc_type != CONV_NONE) {
1192 char_u *conv_str = string_convert(&output_conv, str, &len);
1200 NSString *string = [[NSString alloc]
1201 initWithBytes:str length:len encoding:NSUTF8StringEncoding];
1203 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
1204 [pboard declareTypes:types owner:nil];
1205 BOOL ok = [pboard setString:string forType:NSStringPboardType];
1216 - (oneway void)addReply:(in bycopy NSString *)reply
1217 server:(in byref id <MMVimServerProtocol>)server
1219 //NSLog(@"addReply:%@ server:%@", reply, (id)server);
1221 // Replies might come at any time and in any order so we keep them in an
1222 // array inside a dictionary with the send port used as key.
1224 NSConnection *conn = [(NSDistantObject*)server connectionForProxy];
1225 // HACK! Assume connection uses mach ports.
1226 int port = [(NSMachPort*)[conn sendPort] machPort];
1227 NSNumber *key = [NSNumber numberWithInt:port];
1229 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1231 replies = [NSMutableArray array];
1232 [serverReplyDict setObject:replies forKey:key];
1235 [replies addObject:reply];
1238 - (void)addInput:(in bycopy NSString *)input
1239 client:(in byref id <MMVimClientProtocol>)client
1241 //NSLog(@"addInput:%@ client:%@", input, (id)client);
1243 // NOTE: We don't call addInput: here because it differs from
1244 // server_to_input_buf() in that it always sets the 'silent' flag and we
1245 // don't want the MacVim client/server code to behave differently from
1247 char_u *s = [input vimStringSave];
1248 server_to_input_buf(s);
1251 [self addClient:(id)client];
1254 - (NSString *)evaluateExpression:(in bycopy NSString *)expr
1255 client:(in byref id <MMVimClientProtocol>)client
1257 [self addClient:(id)client];
1258 return [self evaluateExpression:expr];
1261 - (void)registerServerWithName:(NSString *)name
1263 NSString *svrName = name;
1264 NSConnection *svrConn = [NSConnection defaultConnection];
1267 for (i = 0; i < MMServerMax; ++i) {
1268 NSString *connName = [self connectionNameFromServerName:svrName];
1270 if ([svrConn registerName:connName]) {
1271 //NSLog(@"Registered server with name: %@", svrName);
1273 // TODO: Set request/reply time-outs to something else?
1275 // Don't wait for requests (time-out means that the message is
1277 [svrConn setRequestTimeout:0];
1278 //[svrConn setReplyTimeout:MMReplyTimeout];
1279 [svrConn setRootObject:self];
1281 // NOTE: 'serverName' is a global variable
1282 serverName = [svrName vimStringSave];
1284 set_vim_var_string(VV_SEND_SERVER, serverName, -1);
1287 need_maketitle = TRUE;
1289 [self queueMessage:SetServerNameMsgID data:
1290 [svrName dataUsingEncoding:NSUTF8StringEncoding]];
1294 svrName = [NSString stringWithFormat:@"%@%d", name, i+1];
1298 - (BOOL)sendToServer:(NSString *)name string:(NSString *)string
1299 reply:(char_u **)reply port:(int *)port expression:(BOOL)expr
1302 // NOTE: If 'name' equals 'serverName' then the request is local (client
1303 // and server are the same). This case is not handled separately, so a
1304 // connection will be set up anyway (this simplifies the code).
1306 NSConnection *conn = [self connectionForServerName:name];
1309 char_u *s = (char_u*)[name UTF8String];
1311 s = CONVERT_FROM_UTF8(s);
1313 EMSG2(_(e_noserver), s);
1315 CONVERT_FROM_UTF8_FREE(s);
1322 // HACK! Assume connection uses mach ports.
1323 *port = [(NSMachPort*)[conn sendPort] machPort];
1326 id proxy = [conn rootProxy];
1327 [proxy setProtocolForProxy:@protocol(MMVimServerProtocol)];
1331 NSString *eval = [proxy evaluateExpression:string client:self];
1334 *reply = [eval vimStringSave];
1336 *reply = vim_strsave((char_u*)_(e_invexprmsg));
1343 [proxy addInput:string client:self];
1346 @catch (NSException *e) {
1347 NSLog(@"WARNING: Caught exception in %s: \"%@\"", _cmd, e);
1354 - (NSArray *)serverList
1356 NSArray *list = nil;
1358 if ([self connection]) {
1359 id proxy = [connection rootProxy];
1360 [proxy setProtocolForProxy:@protocol(MMAppProtocol)];
1363 list = [proxy serverList];
1365 @catch (NSException *e) {
1366 NSLog(@"Exception caught when listing servers: \"%@\"", e);
1369 EMSG(_("E???: No connection to MacVim, server listing not possible."));
1375 - (NSString *)peekForReplyOnPort:(int)port
1377 //NSLog(@"%s%d", _cmd, port);
1379 NSNumber *key = [NSNumber numberWithInt:port];
1380 NSMutableArray *replies = [serverReplyDict objectForKey:key];
1381 if (replies && [replies count]) {
1382 //NSLog(@" %d replies, topmost is: %@", [replies count],
1383 // [replies objectAtIndex:0]);
1384 return [replies objectAtIndex:0];
1387 //NSLog(@" No replies");
1391 - (NSString *)waitForReplyOnPort:(int)port
1393 //NSLog(@"%s%d", _cmd, port);
1395 NSConnection *conn = [self connectionForServerPort:port];
1399 NSNumber *key = [NSNumber numberWithInt:port];
1400 NSMutableArray *replies = nil;
1401 NSString *reply = nil;
1403 // Wait for reply as long as the connection to the server is valid (unless
1404 // user interrupts wait with Ctrl-C).
1405 while (!got_int && [conn isValid] &&
1406 !(replies = [serverReplyDict objectForKey:key])) {
1407 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1408 beforeDate:[NSDate distantFuture]];
1412 if ([replies count] > 0) {
1413 reply = [[replies objectAtIndex:0] retain];
1414 //NSLog(@" Got reply: %@", reply);
1415 [replies removeObjectAtIndex:0];
1416 [reply autorelease];
1419 if ([replies count] == 0)
1420 [serverReplyDict removeObjectForKey:key];
1426 - (BOOL)sendReply:(NSString *)reply toPort:(int)port
1428 id client = [clientProxyDict objectForKey:[NSNumber numberWithInt:port]];
1431 //NSLog(@"sendReply:%@ toPort:%d", reply, port);
1432 [client addReply:reply server:self];
1435 @catch (NSException *e) {
1436 NSLog(@"WARNING: Exception caught in %s: \"%@\"", _cmd, e);
1439 EMSG2(_("E???: server2client failed; no client with id 0x%x"), port);
1450 - (void)setWaitForAck:(BOOL)yn
1455 - (void)waitForConnectionAcknowledgement
1457 if (!waitForAck) return;
1459 while (waitForAck && !got_int && [connection isValid] && !isTerminating) {
1460 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1461 beforeDate:[NSDate distantFuture]];
1462 //NSLog(@" waitForAck=%d got_int=%d isTerminating=%d isValid=%d",
1463 // waitForAck, got_int, isTerminating, [connection isValid]);
1467 // Never received a connection acknowledgement, so die.
1468 [[NSNotificationCenter defaultCenter] removeObserver:self];
1469 [frontendProxy release]; frontendProxy = nil;
1471 // NOTE: We intentionally do not call mch_exit() since this in turn
1472 // will lead to -[MMBackend exit] getting called which we want to
1474 usleep(MMExitProcessDelay);
1478 [self processInputQueue];
1481 - (oneway void)acknowledgeConnection
1483 //NSLog(@"%s", _cmd);
1491 @implementation MMBackend (Private)
1493 - (void)waitForDialogReturn
1495 // Keep processing the run loop until a dialog returns. To avoid getting
1496 // stuck in an endless loop (could happen if the setDialogReturn: message
1497 // was lost) we also do some paranoia checks.
1499 // Note that in Cocoa the user can still resize windows and select menu
1500 // items while a sheet is being displayed, so we can't just wait for the
1501 // first message to arrive and assume that is the setDialogReturn: call.
1503 while (nil == dialogReturn && !got_int && [connection isValid]
1505 [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode
1506 beforeDate:[NSDate distantFuture]];
1508 // Search for any resize messages on the input queue. All other messages
1509 // on the input queue are dropped. The reason why we single out resize
1510 // messages is because the user may have resized the window while a sheet
1512 int i, count = [inputQueue count];
1514 id textDimData = nil;
1516 for (i = count-2; i >= 0; i -= 2) {
1517 int msgid = [[inputQueue objectAtIndex:i] intValue];
1518 if (SetTextDimensionsMsgID == msgid) {
1519 textDimData = [[inputQueue objectAtIndex:i+1] retain];
1525 [inputQueue removeAllObjects];
1528 [inputQueue addObject:
1529 [NSNumber numberWithInt:SetTextDimensionsMsgID]];
1530 [inputQueue addObject:textDimData];
1531 [textDimData release];
1536 - (void)insertVimStateMessage
1538 // NOTE: This is the place to add Vim state that needs to be accessed from
1539 // MacVim. Do not add state that could potentially require lots of memory
1540 // since this message gets sent each time the output queue is forcibly
1541 // flushed (e.g. storing the currently selected text would be a bad idea).
1542 // We take this approach of "pushing" the state to MacVim to avoid having
1543 // to make synchronous calls from MacVim to Vim in order to get state.
1545 BOOL mmta = curbuf ? curbuf->b_p_mmta : NO;
1547 NSDictionary *vimState = [NSDictionary dictionaryWithObjectsAndKeys:
1548 [[NSFileManager defaultManager] currentDirectoryPath], @"pwd",
1549 [NSNumber numberWithInt:p_mh], @"p_mh",
1550 [NSNumber numberWithBool:[self unusedEditor]], @"unusedEditor",
1551 [NSNumber numberWithBool:mmta], @"p_mmta",
1554 // Put the state before all other messages.
1555 int msgid = SetVimStateMsgID;
1556 [outputQueue insertObject:[vimState dictionaryAsData] atIndex:0];
1557 [outputQueue insertObject:[NSData dataWithBytes:&msgid length:sizeof(int)]
1561 - (void)processInputQueue
1563 if ([inputQueue count] == 0) return;
1565 // NOTE: One of the input events may cause this method to be called
1566 // recursively, so copy the input queue to a local variable and clear the
1567 // queue before starting to process input events (otherwise we could get
1568 // stuck in an endless loop).
1569 NSArray *q = [inputQueue copy];
1570 unsigned i, count = [q count];
1572 [inputQueue removeAllObjects];
1574 for (i = 1; i < count; i+=2) {
1575 int msgid = [[q objectAtIndex:i-1] intValue];
1576 id data = [q objectAtIndex:i];
1577 if ([data isEqual:[NSNull null]])
1580 //NSLog(@"(%d) %s:%s", i, _cmd, MessageStrings[msgid]);
1581 [self handleInputEvent:msgid data:data];
1585 //NSLog(@"Clear input event queue");
1588 - (void)handleInputEvent:(int)msgid data:(NSData *)data
1590 if (InsertTextMsgID == msgid || KeyDownMsgID == msgid ||
1591 CmdKeyMsgID == msgid) {
1593 const void *bytes = [data bytes];
1594 int mods = *((int*)bytes); bytes += sizeof(int);
1595 int len = *((int*)bytes); bytes += sizeof(int);
1596 NSString *key = [[NSString alloc] initWithBytes:bytes
1598 encoding:NSUTF8StringEncoding];
1599 mods = eventModifierFlagsToVimModMask(mods);
1601 if (InsertTextMsgID == msgid)
1602 [self handleInsertText:key];
1604 [self handleKeyDown:key modifiers:mods];
1607 } else if (ScrollWheelMsgID == msgid) {
1609 const void *bytes = [data bytes];
1611 int row = *((int*)bytes); bytes += sizeof(int);
1612 int col = *((int*)bytes); bytes += sizeof(int);
1613 int flags = *((int*)bytes); bytes += sizeof(int);
1614 float dy = *((float*)bytes); bytes += sizeof(float);
1616 int button = MOUSE_5;
1617 if (dy > 0) button = MOUSE_4;
1619 flags = eventModifierFlagsToVimMouseModMask(flags);
1621 int numLines = (int)round(dy);
1622 if (numLines < 0) numLines = -numLines;
1623 if (numLines == 0) numLines = 1;
1625 #ifdef FEAT_GUI_SCROLL_WHEEL_FORCE
1626 gui.scroll_wheel_force = numLines;
1629 gui_send_mouse_event(button, col, row, NO, flags);
1630 } else if (MouseDownMsgID == msgid) {
1632 const void *bytes = [data bytes];
1634 int row = *((int*)bytes); bytes += sizeof(int);
1635 int col = *((int*)bytes); bytes += sizeof(int);
1636 int button = *((int*)bytes); bytes += sizeof(int);
1637 int flags = *((int*)bytes); bytes += sizeof(int);
1638 int count = *((int*)bytes); bytes += sizeof(int);
1640 button = eventButtonNumberToVimMouseButton(button);
1642 flags = eventModifierFlagsToVimMouseModMask(flags);
1643 gui_send_mouse_event(button, col, row, count>1, flags);
1645 } else if (MouseUpMsgID == msgid) {
1647 const void *bytes = [data bytes];
1649 int row = *((int*)bytes); bytes += sizeof(int);
1650 int col = *((int*)bytes); bytes += sizeof(int);
1651 int flags = *((int*)bytes); bytes += sizeof(int);
1653 flags = eventModifierFlagsToVimMouseModMask(flags);
1655 gui_send_mouse_event(MOUSE_RELEASE, col, row, NO, flags);
1656 } else if (MouseDraggedMsgID == msgid) {
1658 const void *bytes = [data bytes];
1660 int row = *((int*)bytes); bytes += sizeof(int);
1661 int col = *((int*)bytes); bytes += sizeof(int);
1662 int flags = *((int*)bytes); bytes += sizeof(int);
1664 flags = eventModifierFlagsToVimMouseModMask(flags);
1666 gui_send_mouse_event(MOUSE_DRAG, col, row, NO, flags);
1667 } else if (MouseMovedMsgID == msgid) {
1668 const void *bytes = [data bytes];
1669 int row = *((int*)bytes); bytes += sizeof(int);
1670 int col = *((int*)bytes); bytes += sizeof(int);
1672 gui_mouse_moved(col, row);
1673 } else if (AddInputMsgID == msgid) {
1674 NSString *string = [[NSString alloc] initWithData:data
1675 encoding:NSUTF8StringEncoding];
1677 [self addInput:string];
1680 } else if (SelectTabMsgID == msgid) {
1682 const void *bytes = [data bytes];
1683 int idx = *((int*)bytes) + 1;
1684 //NSLog(@"Selecting tab %d", idx);
1685 send_tabline_event(idx);
1686 } else if (CloseTabMsgID == msgid) {
1688 const void *bytes = [data bytes];
1689 int idx = *((int*)bytes) + 1;
1690 //NSLog(@"Closing tab %d", idx);
1691 send_tabline_menu_event(idx, TABLINE_MENU_CLOSE);
1692 } else if (AddNewTabMsgID == msgid) {
1693 //NSLog(@"Adding new tab");
1694 send_tabline_menu_event(0, TABLINE_MENU_NEW);
1695 } else if (DraggedTabMsgID == msgid) {
1697 const void *bytes = [data bytes];
1698 // NOTE! The destination index is 0 based, so do not add 1 to make it 1
1700 int idx = *((int*)bytes);
1703 } else if (SetTextDimensionsMsgID == msgid || LiveResizeMsgID == msgid
1704 || SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1706 const void *bytes = [data bytes];
1708 if (SetTextColumnsMsgID != msgid) {
1709 rows = *((int*)bytes); bytes += sizeof(int);
1712 if (SetTextRowsMsgID != msgid) {
1713 cols = *((int*)bytes); bytes += sizeof(int);
1717 if (SetTextRowsMsgID == msgid || SetTextColumnsMsgID == msgid) {
1718 int dim[2] = { rows, cols };
1719 d = [NSData dataWithBytes:dim length:2*sizeof(int)];
1720 msgid = SetTextDimensionsReplyMsgID;
1723 if (SetTextDimensionsMsgID == msgid)
1724 msgid = SetTextDimensionsReplyMsgID;
1726 // NOTE! Vim doesn't call gui_mch_set_shellsize() after
1727 // gui_resize_shell(), so we have to manually set the rows and columns
1728 // here since MacVim doesn't change the rows and columns to avoid
1729 // inconsistent states between Vim and MacVim. The message sent back
1730 // indicates that it is a reply to a message that originated in MacVim
1731 // since we need to be able to determine where a message originated.
1732 [self queueMessage:msgid data:d];
1734 //NSLog(@"[VimTask] Resizing shell to %dx%d.", cols, rows);
1735 gui_resize_shell(cols, rows);
1736 } else if (ExecuteMenuMsgID == msgid) {
1737 NSDictionary *attrs = [NSDictionary dictionaryWithData:data];
1739 NSArray *desc = [attrs objectForKey:@"descriptor"];
1740 vimmenu_T *menu = menu_for_descriptor(desc);
1744 } else if (ToggleToolbarMsgID == msgid) {
1745 [self handleToggleToolbar];
1746 } else if (ScrollbarEventMsgID == msgid) {
1747 [self handleScrollbarEvent:data];
1748 } else if (SetFontMsgID == msgid) {
1749 [self handleSetFont:data];
1750 } else if (VimShouldCloseMsgID == msgid) {
1752 } else if (DropFilesMsgID == msgid) {
1753 [self handleDropFiles:data];
1754 } else if (DropStringMsgID == msgid) {
1755 [self handleDropString:data];
1756 } else if (GotFocusMsgID == msgid) {
1758 [self focusChange:YES];
1759 } else if (LostFocusMsgID == msgid) {
1761 [self focusChange:NO];
1762 } else if (SetMouseShapeMsgID == msgid) {
1763 const void *bytes = [data bytes];
1764 int shape = *((int*)bytes); bytes += sizeof(int);
1765 update_mouseshape(shape);
1766 } else if (XcodeModMsgID == msgid) {
1767 [self handleXcodeMod:data];
1768 } else if (OpenWithArgumentsMsgID == msgid) {
1769 [self handleOpenWithArguments:[NSDictionary dictionaryWithData:data]];
1770 } else if (FindReplaceMsgID == msgid) {
1771 [self handleFindReplace:[NSDictionary dictionaryWithData:data]];
1773 NSLog(@"WARNING: Unknown message received (msgid=%d)", msgid);
1777 + (NSDictionary *)specialKeys
1779 static NSDictionary *specialKeys = nil;
1782 NSBundle *mainBundle = [NSBundle mainBundle];
1783 NSString *path = [mainBundle pathForResource:@"SpecialKeys"
1785 specialKeys = [[NSDictionary alloc] initWithContentsOfFile:path];
1791 - (void)handleInsertText:(NSString *)text
1795 char_u *str = (char_u*)[text UTF8String];
1796 int i, len = [text lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1799 char_u *conv_str = NULL;
1800 if (input_conv.vc_type != CONV_NONE) {
1801 conv_str = string_convert(&input_conv, str, &len);
1807 for (i = 0; i < len; ++i) {
1808 add_to_input_buf(str+i, 1);
1809 if (CSI == str[i]) {
1810 // NOTE: If the converted string contains the byte CSI, then it
1811 // must be followed by the bytes KS_EXTRA, KE_CSI or things
1813 static char_u extra[2] = { KS_EXTRA, KE_CSI };
1814 add_to_input_buf(extra, 2);
1824 - (void)handleKeyDown:(NSString *)key modifiers:(int)mods
1826 // TODO: This code is a horrible mess -- clean up!
1829 char_u *chars = (char_u*)[key UTF8String];
1831 char_u *conv_str = NULL;
1833 int length = [key lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1835 // Special keys (arrow keys, function keys, etc.) are stored in a plist so
1836 // that new keys can easily be added.
1837 NSString *specialString = [[MMBackend specialKeys]
1839 if (specialString && [specialString length] > 1) {
1840 //NSLog(@"special key: %@", specialString);
1841 int ikey = TO_SPECIAL([specialString characterAtIndex:0],
1842 [specialString characterAtIndex:1]);
1844 ikey = simplify_key(ikey, &mods);
1849 special[1] = K_SECOND(ikey);
1850 special[2] = K_THIRD(ikey);
1854 } else if (1 == length && TAB == chars[0]) {
1855 // Tab is a trouble child:
1856 // - <Tab> is added to the input buffer as is
1857 // - <S-Tab> is translated to, {CSI,'k','B'} (i.e. 'Back-tab')
1858 // - <M-Tab> should be 0x80|TAB but this is not valid utf-8 so it needs
1859 // to be converted to utf-8
1860 // - <S-M-Tab> is translated to <S-Tab> with ALT modifier
1861 // - <C-Tab> is reserved by Mac OS X
1862 // - <D-Tab> is reserved by Mac OS X
1867 if (mods & MOD_MASK_SHIFT) {
1868 mods &= ~MOD_MASK_SHIFT;
1870 special[1] = K_SECOND(K_S_TAB);
1871 special[2] = K_THIRD(K_S_TAB);
1873 } else if (mods & MOD_MASK_ALT) {
1874 int mtab = 0x80 | TAB;
1878 special[0] = (mtab >> 6) + 0xc0;
1879 special[1] = mtab & 0xbf;
1887 mods &= ~MOD_MASK_ALT;
1889 } else if (1 == length && chars[0] < 0x80 && (mods & MOD_MASK_ALT)) {
1890 // META key is treated separately. This code was taken from gui_w48.c
1891 // and gui_gtk_x11.c.
1893 int ch = simplify_key(chars[0], &mods);
1895 // Remove the SHIFT modifier for keys where it's already included,
1896 // e.g., '(' and '*'
1897 if (ch < 0x100 && !isalpha(ch) && isprint(ch))
1898 mods &= ~MOD_MASK_SHIFT;
1900 // Interpret the ALT key as making the key META, include SHIFT, etc.
1901 ch = extract_modifiers(ch, &mods);
1907 string[len++] = CSI;
1908 string[len++] = KS_MODIFIER;
1909 string[len++] = mods;
1912 if (IS_SPECIAL(ch)) {
1913 string[len++] = CSI;
1914 string[len++] = K_SECOND(ch);
1915 string[len++] = K_THIRD(ch);
1919 // TODO: What if 'enc' is not "utf-8"?
1920 if (enc_utf8 && (ch & 0x80)) { // convert to utf-8
1921 string[len++] = ch & 0xbf;
1922 string[len-2] = ((unsigned)ch >> 6) + 0xc0;
1923 if (string[len-1] == CSI) {
1924 string[len++] = KS_EXTRA;
1925 string[len++] = (int)KE_CSI;
1931 add_to_input_buf(string, len);
1933 } else if (length > 0) {
1934 unichar c = [key characterAtIndex:0];
1935 //NSLog(@"non-special: %@ (hex=%x, mods=%d)", key,
1936 // [key characterAtIndex:0], mods);
1938 // HACK! In most circumstances the Ctrl and Shift modifiers should be
1939 // cleared since they are already added to the key by the AppKit.
1940 // Unfortunately, the only way to deal with when to clear the modifiers
1941 // or not seems to be to have hard-wired rules like this.
1942 if ( !((' ' == c) || (0xa0 == c) || (mods & MOD_MASK_CMD)
1943 || 0x9 == c || 0xd == c || ESC == c) ) {
1944 mods &= ~MOD_MASK_SHIFT;
1945 mods &= ~MOD_MASK_CTRL;
1946 //NSLog(@"clear shift ctrl");
1950 if (input_conv.vc_type != CONV_NONE) {
1951 conv_str = string_convert(&input_conv, chars, &length);
1958 if (chars && length > 0) {
1960 //NSLog(@"adding mods: %d", mods);
1962 modChars[1] = KS_MODIFIER;
1964 add_to_input_buf(modChars, 3);
1967 //NSLog(@"add to input buf: 0x%x", chars[0]);
1968 // TODO: Check for CSI bytes?
1969 add_to_input_buf(chars, length);
1978 - (void)queueMessage:(int)msgid data:(NSData *)data
1980 //if (msgid != EnableMenuItemMsgID)
1981 // NSLog(@"queueMessage:%s", MessageStrings[msgid]);
1983 [outputQueue addObject:[NSData dataWithBytes:&msgid length:sizeof(int)]];
1985 [outputQueue addObject:data];
1987 [outputQueue addObject:[NSData data]];
1990 - (void)connectionDidDie:(NSNotification *)notification
1992 // If the main connection to MacVim is lost this means that MacVim was
1993 // either quit (by the user chosing Quit on the MacVim menu), or it has
1994 // crashed. In the former case the flag 'isTerminating' is set and we then
1995 // quit cleanly; in the latter case we make sure the swap files are left
1998 // NOTE: This is not called if a Vim controller invalidates its connection.
2000 //NSLog(@"%s isTerminating=%d", _cmd, isTerminating);
2004 getout_preserve_modified(1);
2007 - (void)blinkTimerFired:(NSTimer *)timer
2009 NSTimeInterval timeInterval = 0;
2011 [blinkTimer release];
2014 if (MMBlinkStateOn == blinkState) {
2015 gui_undraw_cursor();
2016 blinkState = MMBlinkStateOff;
2017 timeInterval = blinkOffInterval;
2018 } else if (MMBlinkStateOff == blinkState) {
2019 gui_update_cursor(TRUE, FALSE);
2020 blinkState = MMBlinkStateOn;
2021 timeInterval = blinkOnInterval;
2024 if (timeInterval > 0) {
2026 [[NSTimer scheduledTimerWithTimeInterval:timeInterval target:self
2027 selector:@selector(blinkTimerFired:)
2028 userInfo:nil repeats:NO] retain];
2029 [self flushQueue:YES];
2033 - (void)focusChange:(BOOL)on
2035 gui_focus_change(on);
2038 - (void)handleToggleToolbar
2040 // If 'go' contains 'T', then remove it, else add it.
2042 char_u go[sizeof(GO_ALL)+2];
2047 p = vim_strchr(go, GO_TOOLBAR);
2051 char_u *end = go + len;
2057 go[len] = GO_TOOLBAR;
2061 set_option_value((char_u*)"guioptions", 0, go, 0);
2063 [self redrawScreen];
2066 - (void)handleScrollbarEvent:(NSData *)data
2070 const void *bytes = [data bytes];
2071 long ident = *((long*)bytes); bytes += sizeof(long);
2072 int hitPart = *((int*)bytes); bytes += sizeof(int);
2073 float fval = *((float*)bytes); bytes += sizeof(float);
2074 scrollbar_T *sb = gui_find_scrollbar(ident);
2077 scrollbar_T *sb_info = sb->wp ? &sb->wp->w_scrollbars[0] : sb;
2078 long value = sb_info->value;
2079 long size = sb_info->size;
2080 long max = sb_info->max;
2081 BOOL isStillDragging = NO;
2082 BOOL updateKnob = YES;
2085 case NSScrollerDecrementPage:
2086 value -= (size > 2 ? size - 2 : 1);
2088 case NSScrollerIncrementPage:
2089 value += (size > 2 ? size - 2 : 1);
2091 case NSScrollerDecrementLine:
2094 case NSScrollerIncrementLine:
2097 case NSScrollerKnob:
2098 isStillDragging = YES;
2100 case NSScrollerKnobSlot:
2101 value = (long)(fval * (max - size + 1));
2108 //NSLog(@"value %d -> %d", sb_info->value, value);
2109 gui_drag_scrollbar(sb, value, isStillDragging);
2112 // Dragging the knob or option+clicking automatically updates
2113 // the knob position (on the actual NSScroller), so we only
2114 // need to set the knob position in the other cases.
2116 // Update both the left&right vertical scrollbars.
2117 long identLeft = sb->wp->w_scrollbars[SBAR_LEFT].ident;
2118 long identRight = sb->wp->w_scrollbars[SBAR_RIGHT].ident;
2119 [self setScrollbarThumbValue:value size:size max:max
2120 identifier:identLeft];
2121 [self setScrollbarThumbValue:value size:size max:max
2122 identifier:identRight];
2124 // Update the horizontal scrollbar.
2125 [self setScrollbarThumbValue:value size:size max:max
2132 - (void)handleSetFont:(NSData *)data
2136 const void *bytes = [data bytes];
2137 float pointSize = *((float*)bytes); bytes += sizeof(float);
2138 //unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2139 bytes += sizeof(unsigned); // len not used
2141 NSMutableString *name = [NSMutableString stringWithUTF8String:bytes];
2142 [name appendString:[NSString stringWithFormat:@":h%.2f", pointSize]];
2143 char_u *s = (char_u*)[name UTF8String];
2146 s = CONVERT_FROM_UTF8(s);
2149 set_option_value((char_u*)"guifont", 0, s, 0);
2152 CONVERT_FROM_UTF8_FREE(s);
2155 [self redrawScreen];
2158 - (void)handleDropFiles:(NSData *)data
2160 // TODO: Get rid of this method; instead use Vim script directly. At the
2161 // moment I know how to do this to open files in tabs, but I'm not sure how
2162 // to add the filenames to the command line when in command line mode.
2166 NSMutableDictionary *args = [NSMutableDictionary dictionaryWithData:data];
2169 id obj = [args objectForKey:@"forceOpen"];
2170 BOOL forceOpen = YES;
2172 forceOpen = [obj boolValue];
2174 NSArray *filenames = [args objectForKey:@"filenames"];
2175 if (!(filenames && [filenames count] > 0)) return;
2178 if (!forceOpen && (State & CMDLINE)) {
2179 // HACK! If Vim is in command line mode then the files names
2180 // should be added to the command line, instead of opening the
2181 // files in tabs (unless forceOpen is set). This is taken care of by
2182 // gui_handle_drop().
2183 int n = [filenames count];
2184 char_u **fnames = (char_u **)alloc(n * sizeof(char_u *));
2187 for (i = 0; i < n; ++i)
2188 fnames[i] = [[filenames objectAtIndex:i] vimStringSave];
2190 // NOTE! This function will free 'fnames'.
2191 // HACK! It is assumed that the 'x' and 'y' arguments are
2192 // unused when in command line mode.
2193 gui_handle_drop(0, 0, 0, fnames, n);
2198 [self handleOpenWithArguments:args];
2202 - (void)handleDropString:(NSData *)data
2207 char_u dropkey[3] = { CSI, KS_EXTRA, (char_u)KE_DROP };
2208 const void *bytes = [data bytes];
2209 int len = *((int*)bytes); bytes += sizeof(int);
2210 NSMutableString *string = [NSMutableString stringWithUTF8String:bytes];
2212 // Replace unrecognized end-of-line sequences with \x0a (line feed).
2213 NSRange range = { 0, [string length] };
2214 unsigned n = [string replaceOccurrencesOfString:@"\x0d\x0a"
2215 withString:@"\x0a" options:0
2218 n = [string replaceOccurrencesOfString:@"\x0d" withString:@"\x0a"
2219 options:0 range:range];
2222 len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2223 char_u *s = (char_u*)[string UTF8String];
2225 if (input_conv.vc_type != CONV_NONE)
2226 s = string_convert(&input_conv, s, &len);
2228 dnd_yank_drag_data(s, len);
2230 if (input_conv.vc_type != CONV_NONE)
2233 add_to_input_buf(dropkey, sizeof(dropkey));
2237 - (void)startOdbEditWithArguments:(NSDictionary *)args
2239 #ifdef FEAT_ODB_EDITOR
2240 id obj = [args objectForKey:@"remoteID"];
2243 OSType serverID = [obj unsignedIntValue];
2244 NSString *remotePath = [args objectForKey:@"remotePath"];
2246 NSAppleEventDescriptor *token = nil;
2247 NSData *tokenData = [args objectForKey:@"remoteTokenData"];
2248 obj = [args objectForKey:@"remoteTokenDescType"];
2249 if (tokenData && obj) {
2250 DescType tokenType = [obj unsignedLongValue];
2251 token = [NSAppleEventDescriptor descriptorWithDescriptorType:tokenType
2255 NSArray *filenames = [args objectForKey:@"filenames"];
2256 unsigned i, numFiles = [filenames count];
2257 for (i = 0; i < numFiles; ++i) {
2258 NSString *filename = [filenames objectAtIndex:i];
2259 char_u *s = [filename vimStringSave];
2260 buf_T *buf = buflist_findname(s);
2264 if (buf->b_odb_token) {
2265 [(NSAppleEventDescriptor*)(buf->b_odb_token) release];
2266 buf->b_odb_token = NULL;
2269 if (buf->b_odb_fname) {
2270 vim_free(buf->b_odb_fname);
2271 buf->b_odb_fname = NULL;
2274 buf->b_odb_server_id = serverID;
2277 buf->b_odb_token = [token retain];
2279 buf->b_odb_fname = [remotePath vimStringSave];
2281 NSLog(@"WARNING: Could not find buffer '%@' for ODB editing.",
2285 #endif // FEAT_ODB_EDITOR
2288 - (void)handleXcodeMod:(NSData *)data
2291 const void *bytes = [data bytes];
2292 DescType type = *((DescType*)bytes); bytes += sizeof(DescType);
2293 unsigned len = *((unsigned*)bytes); bytes += sizeof(unsigned);
2297 NSAppleEventDescriptor *replyEvent = [NSAppleEventDescriptor
2298 descriptorWithDescriptorType:type
2304 - (void)handleOpenWithArguments:(NSDictionary *)args
2306 // ARGUMENT: DESCRIPTION:
2307 // -------------------------------------------------------------
2308 // filenames list of filenames
2309 // dontOpen don't open files specified in above argument
2310 // layout which layout to use to open files
2311 // selectionRange range of lines to select
2312 // searchText string to search for
2313 // cursorLine line to position the cursor on
2314 // cursorColumn column to position the cursor on
2315 // (only valid when "cursorLine" is set)
2316 // remoteID ODB parameter
2317 // remotePath ODB parameter
2318 // remoteTokenDescType ODB parameter
2319 // remoteTokenData ODB parameter
2321 //NSLog(@"%s%@ (starting=%d)", _cmd, args, starting);
2323 NSArray *filenames = [args objectForKey:@"filenames"];
2324 int i, numFiles = filenames ? [filenames count] : 0;
2325 BOOL openFiles = ![[args objectForKey:@"dontOpen"] boolValue];
2326 int layout = [[args objectForKey:@"layout"] intValue];
2328 // Change to directory of first file to open if this is an "unused" editor
2329 // (but do not do this if editing remotely).
2330 if (openFiles && numFiles > 0 && ![args objectForKey:@"remoteID"]
2331 && (starting || [self unusedEditor]) ) {
2332 char_u *s = [[filenames objectAtIndex:0] vimStringSave];
2338 // When Vim is starting we simply add the files to be opened to the
2339 // global arglist and Vim will take care of opening them for us.
2340 if (openFiles && numFiles > 0) {
2341 for (i = 0; i < numFiles; i++) {
2342 NSString *fname = [filenames objectAtIndex:i];
2345 if (ga_grow(&global_alist.al_ga, 1) == FAIL
2346 || (p = [fname vimStringSave]) == NULL)
2347 exit(2); // See comment in -[MMBackend exit]
2349 alist_add(&global_alist, p, 2);
2352 // Vim will take care of arranging the files added to the arglist
2353 // in windows or tabs; all we must do is to specify which layout to
2355 initialWindowLayout = layout;
2358 // When Vim is already open we resort to some trickery to open the
2359 // files with the specified layout.
2361 // TODO: Figure out a better way to handle this?
2362 if (openFiles && numFiles > 0) {
2363 BOOL oneWindowInTab = topframe ? YES
2364 : (topframe->fr_layout == FR_LEAF);
2365 BOOL bufChanged = NO;
2366 BOOL bufHasFilename = NO;
2368 bufChanged = curbufIsChanged();
2369 bufHasFilename = curbuf->b_ffname != NULL;
2372 // Temporarily disable flushing since the following code may
2373 // potentially cause multiple redraws.
2374 flushDisabled = YES;
2376 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2377 if (WIN_TABS == layout && !onlyOneTab) {
2378 // By going to the last tabpage we ensure that the new tabs
2379 // will appear last (if this call is left out, the taborder
2384 // Make sure we're in normal mode first.
2385 [self addInput:@"<C-\\><C-N>"];
2388 // With "split layout" we open a new tab before opening
2389 // multiple files if the current tab has more than one window
2390 // or if there is exactly one window but whose buffer has a
2391 // filename. (The :drop command ensures modified buffers get
2392 // their own window.)
2393 if ((WIN_HOR == layout || WIN_VER == layout) &&
2394 (!oneWindowInTab || bufHasFilename))
2395 [self addInput:@":tabnew<CR>"];
2397 // The files are opened by constructing a ":drop ..." command
2398 // and executing it.
2399 NSMutableString *cmd = (WIN_TABS == layout)
2400 ? [NSMutableString stringWithString:@":tab drop"]
2401 : [NSMutableString stringWithString:@":drop"];
2403 for (i = 0; i < numFiles; ++i) {
2404 NSString *file = [filenames objectAtIndex:i];
2405 file = [file stringByEscapingSpecialFilenameCharacters];
2406 [cmd appendString:@" "];
2407 [cmd appendString:file];
2410 // Temporarily clear 'suffixes' so that the files are opened in
2411 // the same order as they appear in the "filenames" array.
2412 [self addInput:@":let mvim_oldsu=&su|set su=<CR>"];
2414 [self addInput:cmd];
2416 // Split the view into multiple windows if requested.
2417 if (WIN_HOR == layout)
2418 [self addInput:@"|sall"];
2419 else if (WIN_VER == layout)
2420 [self addInput:@"|vert sall"];
2422 // Restore the old value of 'suffixes'.
2423 [self addInput:@"|let &su=mvim_oldsu|unlet mvim_oldsu<CR>"];
2425 // When opening one file we try to reuse the current window,
2426 // but not if its buffer is modified or has a filename.
2427 // However, the 'arglist' layout always opens the file in the
2429 NSString *file = [[filenames lastObject]
2430 stringByEscapingSpecialFilenameCharacters];
2432 if (WIN_HOR == layout) {
2433 if (!(bufHasFilename || bufChanged))
2434 cmd = [NSString stringWithFormat:@":e %@", file];
2436 cmd = [NSString stringWithFormat:@":sp %@", file];
2437 } else if (WIN_VER == layout) {
2438 if (!(bufHasFilename || bufChanged))
2439 cmd = [NSString stringWithFormat:@":e %@", file];
2441 cmd = [NSString stringWithFormat:@":vsp %@", file];
2442 } else if (WIN_TABS == layout) {
2443 if (oneWindowInTab && !(bufHasFilename || bufChanged))
2444 cmd = [NSString stringWithFormat:@":e %@", file];
2446 cmd = [NSString stringWithFormat:@":tabe %@", file];
2448 // (The :drop command will split if there is a modified
2450 cmd = [NSString stringWithFormat:@":drop %@", file];
2453 [self addInput:cmd];
2454 [self addInput:@"<CR>"];
2457 // Force screen redraw (does it have to be this complicated?).
2458 // (This code was taken from the end of gui_handle_drop().)
2459 update_screen(NOT_VALID);
2462 gui_update_cursor(FALSE, FALSE);
2469 if ([args objectForKey:@"remoteID"]) {
2470 // NOTE: We have to delay processing any ODB related arguments since
2471 // the file(s) may not be opened until the input buffer is processed.
2472 [self performSelectorOnMainThread:@selector(startOdbEditWithArguments:)
2477 NSString *lineString = [args objectForKey:@"cursorLine"];
2478 if (lineString && [lineString intValue] > 0) {
2479 NSString *columnString = [args objectForKey:@"cursorColumn"];
2480 if (!(columnString && [columnString intValue] > 0))
2481 columnString = @"1";
2483 NSString *cmd = [NSString stringWithFormat:@"<C-\\><C-N>:cal "
2484 "cursor(%@,%@)|norm! zz<CR>:f<CR>", lineString, columnString];
2485 [self addInput:cmd];
2488 NSString *rangeString = [args objectForKey:@"selectionRange"];
2490 // Build a command line string that will select the given range of
2491 // lines. If range.length == 0, then position the cursor on the given
2492 // line but do not select.
2493 NSRange range = NSRangeFromString(rangeString);
2495 if (range.length > 0) {
2496 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dGz.0",
2497 NSMaxRange(range), range.location];
2499 cmd = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.0",
2503 [self addInput:cmd];
2506 NSString *searchText = [args objectForKey:@"searchText"];
2508 // TODO: Searching is an exclusive motion, so if the pattern would
2509 // match on row 0 column 0 then this pattern will miss that match.
2510 [self addInput:[NSString stringWithFormat:@"<C-\\><C-N>gg/\\c%@<CR>",
2515 - (BOOL)checkForModifiedBuffers
2518 for (buf = firstbuf; buf != NULL; buf = buf->b_next) {
2519 if (bufIsChanged(buf)) {
2527 - (void)addInput:(NSString *)input
2529 // NOTE: This code is essentially identical to server_to_input_buf(),
2530 // except the 'silent' flag is TRUE in the call to ins_typebuf() below.
2531 char_u *str = [input vimStringSave];
2533 char_u *cpo_save = p_cpo;
2537 /* Set 'cpoptions' the way we want it.
2538 * B set - backslashes are *not* treated specially
2539 * k set - keycodes are *not* reverse-engineered
2540 * < unset - <Key> sequences *are* interpreted
2541 * The last but one parameter of replace_termcodes() is TRUE so that the
2542 * <lt> sequence is recognised - needed for a real backslash.
2544 p_cpo = (char_u *)"Bk";
2545 str = replace_termcodes((char_u *)str, &ptr, FALSE, TRUE, FALSE);
2548 if (*ptr != NUL) /* trailing CTRL-V results in nothing */
2551 * Add the string to the input stream.
2552 * Can't use add_to_input_buf() here, we now have K_SPECIAL bytes.
2554 * First clear typed characters from the typeahead buffer, there could
2555 * be half a mapping there. Then append to the existing string, so
2556 * that multiple commands from a client are concatenated.
2558 if (typebuf.tb_maplen < typebuf.tb_len)
2559 del_typebuf(typebuf.tb_len - typebuf.tb_maplen, typebuf.tb_maplen);
2560 (void)ins_typebuf(str, REMAP_NONE, typebuf.tb_len, TRUE, TRUE);
2562 /* Let input_available() know we inserted text in the typeahead
2564 typebuf_was_filled = TRUE;
2566 vim_free((char_u *)ptr);
2570 - (BOOL)unusedEditor
2572 BOOL oneWindowInTab = topframe ? YES
2573 : (topframe->fr_layout == FR_LEAF);
2574 BOOL bufChanged = NO;
2575 BOOL bufHasFilename = NO;
2577 bufChanged = curbufIsChanged();
2578 bufHasFilename = curbuf->b_ffname != NULL;
2581 BOOL onlyOneTab = (first_tabpage->tp_next == NULL);
2583 return onlyOneTab && oneWindowInTab && !bufChanged && !bufHasFilename;
2586 - (void)redrawScreen
2588 // Force screen redraw (does it have to be this complicated?).
2589 redraw_all_later(CLEAR);
2590 update_screen(NOT_VALID);
2593 gui_update_cursor(FALSE, FALSE);
2595 // HACK! The cursor is not put back at the command line by the above
2596 // "redraw commands". The following test seems to do the trick though.
2597 if (State & CMDLINE)
2601 - (void)handleFindReplace:(NSDictionary *)args
2605 NSString *findString = [args objectForKey:@"find"];
2606 if (!findString) return;
2608 char_u *find = [findString vimStringSave];
2609 char_u *replace = [[args objectForKey:@"replace"] vimStringSave];
2610 int flags = [[args objectForKey:@"flags"] intValue];
2612 // NOTE: The flag 0x100 is used to indicate a backward search.
2613 gui_do_findrepl(flags, find, replace, (flags & 0x100) == 0);
2619 @end // MMBackend (Private)
2624 @implementation MMBackend (ClientServer)
2626 - (NSString *)connectionNameFromServerName:(NSString *)name
2628 NSString *bundlePath = [[NSBundle mainBundle] bundlePath];
2630 return [[NSString stringWithFormat:@"%@.%@", bundlePath, name]
2634 - (NSConnection *)connectionForServerName:(NSString *)name
2636 // TODO: Try 'name%d' if 'name' fails.
2637 NSString *connName = [self connectionNameFromServerName:name];
2638 NSConnection *svrConn = [connectionNameDict objectForKey:connName];
2641 svrConn = [NSConnection connectionWithRegisteredName:connName
2643 // Try alternate server...
2644 if (!svrConn && alternateServerName) {
2645 //NSLog(@" trying to connect to alternate server: %@",
2646 // alternateServerName);
2647 connName = [self connectionNameFromServerName:alternateServerName];
2648 svrConn = [NSConnection connectionWithRegisteredName:connName
2652 // Try looking for alternate servers...
2654 //NSLog(@" looking for alternate servers...");
2655 NSString *alt = [self alternateServerNameForName:name];
2656 if (alt != alternateServerName) {
2657 //NSLog(@" found alternate server: %@", string);
2658 [alternateServerName release];
2659 alternateServerName = [alt copy];
2663 // Try alternate server again...
2664 if (!svrConn && alternateServerName) {
2665 //NSLog(@" trying to connect to alternate server: %@",
2666 // alternateServerName);
2667 connName = [self connectionNameFromServerName:alternateServerName];
2668 svrConn = [NSConnection connectionWithRegisteredName:connName
2673 [connectionNameDict setObject:svrConn forKey:connName];
2675 //NSLog(@"Adding %@ as connection observer for %@", self, svrConn);
2676 [[NSNotificationCenter defaultCenter] addObserver:self
2677 selector:@selector(serverConnectionDidDie:)
2678 name:NSConnectionDidDieNotification object:svrConn];
2685 - (NSConnection *)connectionForServerPort:(int)port
2688 NSEnumerator *e = [connectionNameDict objectEnumerator];
2690 while ((conn = [e nextObject])) {
2691 // HACK! Assume connection uses mach ports.
2692 if (port == [(NSMachPort*)[conn sendPort] machPort])
2699 - (void)serverConnectionDidDie:(NSNotification *)notification
2701 //NSLog(@"%s%@", _cmd, notification);
2703 NSConnection *svrConn = [notification object];
2705 //NSLog(@"Removing %@ as connection observer from %@", self, svrConn);
2706 [[NSNotificationCenter defaultCenter]
2708 name:NSConnectionDidDieNotification
2711 [connectionNameDict removeObjectsForKeys:
2712 [connectionNameDict allKeysForObject:svrConn]];
2714 // HACK! Assume connection uses mach ports.
2715 int port = [(NSMachPort*)[svrConn sendPort] machPort];
2716 NSNumber *key = [NSNumber numberWithInt:port];
2718 [clientProxyDict removeObjectForKey:key];
2719 [serverReplyDict removeObjectForKey:key];
2722 - (void)addClient:(NSDistantObject *)client
2724 NSConnection *conn = [client connectionForProxy];
2725 // HACK! Assume connection uses mach ports.
2726 int port = [(NSMachPort*)[conn sendPort] machPort];
2727 NSNumber *key = [NSNumber numberWithInt:port];
2729 if (![clientProxyDict objectForKey:key]) {
2730 [client setProtocolForProxy:@protocol(MMVimClientProtocol)];
2731 [clientProxyDict setObject:client forKey:key];
2734 // NOTE: 'clientWindow' is a global variable which is used by <client>
2735 clientWindow = port;
2738 - (NSString *)alternateServerNameForName:(NSString *)name
2740 if (!(name && [name length] > 0))
2743 // Only look for alternates if 'name' doesn't end in a digit.
2744 unichar lastChar = [name characterAtIndex:[name length]-1];
2745 if (lastChar >= '0' && lastChar <= '9')
2748 // Look for alternates among all current servers.
2749 NSArray *list = [self serverList];
2750 if (!(list && [list count] > 0))
2753 // Filter out servers starting with 'name' and ending with a number. The
2754 // (?i) pattern ensures that the match is case insensitive.
2755 NSString *pat = [NSString stringWithFormat:@"(?i)%@[0-9]+\\z", name];
2756 NSPredicate *pred = [NSPredicate predicateWithFormat:
2757 @"SELF MATCHES %@", pat];
2758 list = [list filteredArrayUsingPredicate:pred];
2759 if ([list count] > 0) {
2760 list = [list sortedArrayUsingSelector:@selector(serverNameCompare:)];
2761 return [list objectAtIndex:0];
2767 @end // MMBackend (ClientServer)
2772 @implementation NSString (MMServerNameCompare)
2773 - (NSComparisonResult)serverNameCompare:(NSString *)string
2775 return [self compare:string
2776 options:NSCaseInsensitiveSearch|NSNumericSearch];
2783 static int eventModifierFlagsToVimModMask(int modifierFlags)
2787 if (modifierFlags & NSShiftKeyMask)
2788 modMask |= MOD_MASK_SHIFT;
2789 if (modifierFlags & NSControlKeyMask)
2790 modMask |= MOD_MASK_CTRL;
2791 if (modifierFlags & NSAlternateKeyMask)
2792 modMask |= MOD_MASK_ALT;
2793 if (modifierFlags & NSCommandKeyMask)
2794 modMask |= MOD_MASK_CMD;
2799 static int eventModifierFlagsToVimMouseModMask(int modifierFlags)
2803 if (modifierFlags & NSShiftKeyMask)
2804 modMask |= MOUSE_SHIFT;
2805 if (modifierFlags & NSControlKeyMask)
2806 modMask |= MOUSE_CTRL;
2807 if (modifierFlags & NSAlternateKeyMask)
2808 modMask |= MOUSE_ALT;
2813 static int eventButtonNumberToVimMouseButton(int buttonNumber)
2815 static int mouseButton[] = { MOUSE_LEFT, MOUSE_RIGHT, MOUSE_MIDDLE };
2817 return (buttonNumber >= 0 && buttonNumber < 3)
2818 ? mouseButton[buttonNumber] : -1;
2823 // This function is modeled after the VimToPython function found in if_python.c
2824 // NB This does a deep copy by value, it does not lookup references like the
2825 // VimToPython function does. This is because I didn't want to deal with the
2826 // retain cycles that this would create, and we can cover 99% of the use cases
2827 // by ignoring it. If we ever switch to using GC in MacVim then this
2828 // functionality can be implemented easily.
2829 static id vimToCocoa(typval_T * tv, int depth)
2835 // Avoid infinite recursion
2840 if (tv->v_type == VAR_STRING) {
2841 char_u * val = tv->vval.v_string;
2842 // val can be NULL if the string is empty
2844 result = [NSString string];
2847 val = CONVERT_TO_UTF8(val);
2849 result = [NSString stringWithUTF8String:(char*)val];
2851 CONVERT_TO_UTF8_FREE(val);
2854 } else if (tv->v_type == VAR_NUMBER) {
2855 // looks like sizeof(varnumber_T) is always <= sizeof(long)
2856 result = [NSNumber numberWithLong:(long)tv->vval.v_number];
2857 } else if (tv->v_type == VAR_LIST) {
2858 list_T * list = tv->vval.v_list;
2861 NSMutableArray * arr = result = [NSMutableArray array];
2864 for (curr = list->lv_first; curr != NULL; curr = curr->li_next) {
2865 newObj = vimToCocoa(&curr->li_tv, depth + 1);
2866 [arr addObject:newObj];
2869 } else if (tv->v_type == VAR_DICT) {
2870 NSMutableDictionary * dict = result = [NSMutableDictionary dictionary];
2872 if (tv->vval.v_dict != NULL) {
2873 hashtab_T * ht = &tv->vval.v_dict->dv_hashtab;
2874 int todo = ht->ht_used;
2878 for (hi = ht->ht_array; todo > 0; ++hi) {
2879 if (!HASHITEM_EMPTY(hi)) {
2882 di = dict_lookup(hi);
2883 newObj = vimToCocoa(&di->di_tv, depth + 1);
2885 char_u * keyval = hi->hi_key;
2887 keyval = CONVERT_TO_UTF8(keyval);
2889 NSString * key = [NSString stringWithUTF8String:(char*)keyval];
2891 CONVERT_TO_UTF8_FREE(keyval);
2893 [dict setObject:newObj forKey:key];
2897 } else { // only func refs should fall into this category?
2905 // This function is modeled after eval_client_expr_to_string found in main.c
2906 // Returns nil if there was an error evaluating the expression, and writes a
2907 // message to errorStr.
2908 // TODO Get the error that occurred while evaluating the expression in vim
2910 static id evalExprCocoa(NSString * expr, NSString ** errstr)
2913 char_u *s = (char_u*)[expr UTF8String];
2916 s = CONVERT_FROM_UTF8(s);
2919 int save_dbl = debug_break_level;
2920 int save_ro = redir_off;
2922 debug_break_level = -1;
2926 typval_T * tvres = eval_expr(s, NULL);
2928 debug_break_level = save_dbl;
2929 redir_off = save_ro;
2936 CONVERT_FROM_UTF8_FREE(s);
2941 gui_update_cursor(FALSE, FALSE);
2944 if (tvres == NULL) {
2946 *errstr = @"Expression evaluation failed.";
2949 id res = vimToCocoa(tvres, 1);
2954 *errstr = @"Conversion to cocoa values failed.";
2962 @implementation NSString (VimStrings)
2964 + (id)stringWithVimString:(char_u *)s
2966 // This method ensures a non-nil string is returned. If 's' cannot be
2967 // converted to a utf-8 string it is assumed to be latin-1. If conversion
2968 // still fails an empty NSString is returned.
2969 NSString *string = nil;
2972 s = CONVERT_TO_UTF8(s);
2974 string = [NSString stringWithUTF8String:(char*)s];
2976 // HACK! Apparently 's' is not a valid utf-8 string, maybe it is
2978 string = [NSString stringWithCString:(char*)s
2979 encoding:NSISOLatin1StringEncoding];
2982 CONVERT_TO_UTF8_FREE(s);
2986 return string != nil ? string : [NSString string];
2989 - (char_u *)vimStringSave
2991 char_u *s = (char_u*)[self UTF8String], *ret = NULL;
2994 s = CONVERT_FROM_UTF8(s);
2996 ret = vim_strsave(s);
2998 CONVERT_FROM_UTF8_FREE(s);
3004 @end // NSString (VimStrings)