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 * MMAppController is the delegate of NSApp and as such handles file open
14 * requests, application termination, etc. It sets up a named NSConnection on
15 * which it listens to incoming connections from Vim processes. It also
16 * coordinates all MMVimControllers.
18 * A new Vim process is started by calling launchVimProcessWithArguments:.
19 * When the Vim process is initialized it notifies the app controller by
20 * sending a connectBackend:pid: message. At this point a new MMVimController
21 * is allocated. Afterwards, the Vim process communicates directly with its
24 * A Vim process started from the command line connects directly by sending the
25 * connectBackend:pid: message (launchVimProcessWithArguments: is never called
29 #import "MMAppController.h"
30 #import "MMVimController.h"
31 #import "MMWindowController.h"
36 // Default timeout intervals on all connections.
37 static NSTimeInterval MMRequestTimeout = 5;
38 static NSTimeInterval MMReplyTimeout = 5;
41 #pragma options align=mac68k
44 short unused1; // 0 (not used)
45 short lineNum; // line to select (< 0 to specify range)
46 long startRange; // start of selection range (if line < 0)
47 long endRange; // end of selection range (if line < 0)
48 long unused2; // 0 (not used)
49 long theDate; // modification date/time
51 #pragma options align=reset
54 @interface MMAppController (MMServices)
55 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
56 error:(NSString **)error;
57 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
58 error:(NSString **)error;
62 @interface MMAppController (Private)
63 - (MMVimController *)keyVimController;
64 - (MMVimController *)topmostVimController;
65 - (int)launchVimProcessWithArguments:(NSArray *)args;
66 - (NSArray *)filterFilesAndNotify:(NSArray *)files;
67 - (NSArray *)filterOpenFiles:(NSArray *)filenames remote:(OSType)theID
69 token:(NSAppleEventDescriptor *)token
70 selectionRange:(MMSelectionRange *)selRange;
71 - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
72 replyEvent:(NSAppleEventDescriptor *)reply;
73 - (NSString *)inputStringFromSelectionRange:(MMSelectionRange *)selRange;
76 @interface NSMenu (MMExtras)
77 - (void)recurseSetAutoenablesItems:(BOOL)on;
80 @interface NSNumber (MMExtras)
86 @implementation MMAppController
90 NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
91 [NSNumber numberWithBool:NO], MMNoWindowKey,
92 [NSNumber numberWithInt:64], MMTabMinWidthKey,
93 [NSNumber numberWithInt:6*64], MMTabMaxWidthKey,
94 [NSNumber numberWithInt:132], MMTabOptimumWidthKey,
95 [NSNumber numberWithInt:2], MMTextInsetLeftKey,
96 [NSNumber numberWithInt:1], MMTextInsetRightKey,
97 [NSNumber numberWithInt:1], MMTextInsetTopKey,
98 [NSNumber numberWithInt:1], MMTextInsetBottomKey,
99 [NSNumber numberWithBool:NO], MMTerminateAfterLastWindowClosedKey,
100 @"MMTypesetter", MMTypesetterKey,
101 [NSNumber numberWithFloat:1], MMCellWidthMultiplierKey,
102 [NSNumber numberWithFloat:-1], MMBaselineOffsetKey,
103 [NSNumber numberWithBool:YES], MMTranslateCtrlClickKey,
104 [NSNumber numberWithBool:NO], MMOpenFilesInTabsKey,
105 [NSNumber numberWithBool:NO], MMNoFontSubstitutionKey,
106 [NSNumber numberWithBool:NO], MMLoginShellKey,
109 [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
111 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
112 [NSApp registerServicesMenuSendTypes:types returnTypes:types];
117 if ((self = [super init])) {
118 fontContainerRef = loadFonts();
120 vimControllers = [NSMutableArray new];
121 pidArguments = [NSMutableDictionary new];
123 // NOTE! If the name of the connection changes here it must also be
124 // updated in MMBackend.m.
125 NSConnection *connection = [NSConnection defaultConnection];
126 NSString *name = [NSString stringWithFormat:@"%@-connection",
127 [[NSBundle mainBundle] bundleIdentifier]];
128 //NSLog(@"Registering connection with name '%@'", name);
129 if ([connection registerName:name]) {
130 [connection setRequestTimeout:MMRequestTimeout];
131 [connection setReplyTimeout:MMReplyTimeout];
132 [connection setRootObject:self];
134 // NOTE: When the user is resizing the window the AppKit puts the
135 // run loop in event tracking mode. Unless the connection listens
136 // to request in this mode, live resizing won't work.
137 [connection addRequestMode:NSEventTrackingRunLoopMode];
139 NSLog(@"WARNING: Failed to register connection with name '%@'",
149 //NSLog(@"MMAppController dealloc");
151 [pidArguments release];
152 [vimControllers release];
153 [openSelectionString release];
158 - (void)applicationWillFinishLaunching:(NSNotification *)notification
160 [[NSAppleEventManager sharedAppleEventManager]
162 andSelector:@selector(handleXcodeModEvent:replyEvent:)
167 - (void)applicationDidFinishLaunching:(NSNotification *)notification
169 [NSApp setServicesProvider:self];
172 - (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
174 // NOTE! This way it possible to start the app with the command-line
175 // argument '-nowindow yes' and no window will be opened by default.
176 untitledWindowOpening =
177 ![[NSUserDefaults standardUserDefaults] boolForKey:MMNoWindowKey];
178 return untitledWindowOpening;
181 - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
183 [self newWindow:self];
187 - (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
190 NSString *remotePath = nil;
191 NSAppleEventManager *aem;
192 NSAppleEventDescriptor *remoteToken = nil;
193 NSAppleEventDescriptor *odbdesc = nil;
194 NSAppleEventDescriptor *xcodedesc = nil;
195 MMSelectionRange *selRange = NULL;
197 aem = [NSAppleEventManager sharedAppleEventManager];
198 odbdesc = [aem currentAppleEvent];
199 if (![odbdesc paramDescriptorForKeyword:keyFileSender]) {
200 // The ODB paramaters may hide inside the 'keyAEPropData' descriptor.
201 odbdesc = [odbdesc paramDescriptorForKeyword:keyAEPropData];
202 if (![odbdesc paramDescriptorForKeyword:keyFileSender])
207 remoteID = [[odbdesc paramDescriptorForKeyword:keyFileSender]
209 remotePath = [[odbdesc paramDescriptorForKeyword:keyFileCustomPath]
211 remoteToken = [[odbdesc paramDescriptorForKeyword:keyFileSenderToken]
215 xcodedesc = [[aem currentAppleEvent]
216 paramDescriptorForKeyword:keyAEPosition];
218 selRange = (MMSelectionRange*)[[xcodedesc data] bytes];
220 filenames = [self filterOpenFiles:filenames remote:remoteID path:remotePath
221 token:remoteToken selectionRange:selRange];
222 if ([filenames count]) {
224 BOOL openInTabs = [[NSUserDefaults standardUserDefaults]
225 boolForKey:MMOpenFilesInTabsKey];
227 if (openInTabs && (vc = [self topmostVimController])) {
228 // Open files in tabs in the topmost window.
229 [vc dropFiles:filenames forceOpen:YES];
231 [vc odbEdit:filenames server:remoteID path:remotePath
234 [vc addVimInput:[self inputStringFromSelectionRange:selRange]];
236 // Open files in tabs in a new window.
237 NSMutableArray *args = [NSMutableArray arrayWithObject:@"-p"];
238 [args addObjectsFromArray:filenames];
239 int pid = [self launchVimProcessWithArguments:args];
241 // The Vim process starts asynchronously. Some arguments cannot be
242 // on the command line, so store them in a dictionary and pass them
243 // to the process once it has started.
245 // TODO: If the Vim process fails to start, or if it changes PID,
246 // then the memory allocated for these parameters will leak.
247 // Ensure that this cannot happen or somehow detect it.
248 NSMutableDictionary *argDict = nil;
250 // The remote token can be arbitrary data so it is cannot
251 // (without encoding it as text) be passed on the command line.
252 argDict = [NSMutableDictionary dictionaryWithObjectsAndKeys:
253 filenames, @"filenames",
254 [NSNumber numberWithUnsignedInt:remoteID], @"remoteID",
257 [argDict setObject:remotePath forKey:@"remotePath"];
259 [argDict setObject:remoteToken forKey:@"remoteToken"];
264 argDict = [NSMutableDictionary
265 dictionaryWithObject:[xcodedesc data]
266 forKey:@"selectionRangeData"];
268 [argDict setObject:[xcodedesc data]
269 forKey:@"selectionRangeData"];
273 [pidArguments setObject:argDict
274 forKey:[NSNumber numberWithInt:pid]];
278 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
279 // NSApplicationDelegateReplySuccess = 0,
280 // NSApplicationDelegateReplyCancel = 1,
281 // NSApplicationDelegateReplyFailure = 2
284 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
286 return [[NSUserDefaults standardUserDefaults]
287 boolForKey:MMTerminateAfterLastWindowClosedKey];
290 - (NSApplicationTerminateReply)applicationShouldTerminate:
291 (NSApplication *)sender
293 // TODO: Follow Apple's guidelines for 'Graceful Application Termination'
294 // (in particular, allow user to review changes and save).
295 int reply = NSTerminateNow;
296 BOOL modifiedBuffers = NO;
298 // Go through windows, checking for modified buffers. (Each Vim process
299 // tells MacVim when any buffer has been modified and MacVim sets the
300 // 'documentEdited' flag of the window correspondingly.)
301 NSEnumerator *e = [[NSApp windows] objectEnumerator];
303 while ((window = [e nextObject])) {
304 if ([window isDocumentEdited]) {
305 modifiedBuffers = YES;
310 if (modifiedBuffers) {
311 NSAlert *alert = [[NSAlert alloc] init];
312 [alert addButtonWithTitle:@"Quit"];
313 [alert addButtonWithTitle:@"Cancel"];
314 [alert setMessageText:@"Quit without saving?"];
315 [alert setInformativeText:@"There are modified buffers, "
316 "if you quit now all changes will be lost. Quit anyway?"];
317 [alert setAlertStyle:NSWarningAlertStyle];
319 if ([alert runModal] != NSAlertFirstButtonReturn)
320 reply = NSTerminateCancel;
325 // Tell all Vim processes to terminate now (otherwise they'll leave swap
327 if (NSTerminateNow == reply) {
328 e = [vimControllers objectEnumerator];
330 while ((vc = [e nextObject]))
331 [vc sendMessage:TerminateNowMsgID data:nil];
337 - (void)applicationWillTerminate:(NSNotification *)notification
339 [[NSAppleEventManager sharedAppleEventManager]
340 removeEventHandlerForEventClass:'KAHL'
343 // This will invalidate all connections (since they were spawned from the
344 // default connection).
345 [[NSConnection defaultConnection] invalidate];
347 // Send a SIGINT to all running Vim processes, so that they are sure to
348 // receive the connectionDidDie: notification (a process has to be checking
349 // the run-loop for this to happen).
350 unsigned i, count = [vimControllers count];
351 for (i = 0; i < count; ++i) {
352 MMVimController *controller = [vimControllers objectAtIndex:i];
353 int pid = [controller pid];
358 if (fontContainerRef) {
359 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
360 fontContainerRef = 0;
363 // TODO: Is this a correct way of releasing the MMAppController?
364 // (It doesn't seem like dealloc is ever called.)
365 [NSApp setDelegate:nil];
369 - (void)removeVimController:(id)controller
371 //NSLog(@"%s%@", _cmd, controller);
373 [[controller windowController] close];
375 [vimControllers removeObject:controller];
377 if (![vimControllers count]) {
378 // Turn on autoenabling of menus (because no Vim is open to handle it),
379 // but do not touch the MacVim menu. Note that the menus must be
380 // enabled first otherwise autoenabling does not work.
381 NSMenu *mainMenu = [NSApp mainMenu];
382 int i, count = [mainMenu numberOfItems];
383 for (i = 1; i < count; ++i) {
384 NSMenuItem *item = [mainMenu itemAtIndex:i];
385 [item setEnabled:YES];
386 [[item submenu] recurseSetAutoenablesItems:YES];
391 - (void)windowControllerWillOpen:(MMWindowController *)windowController
393 NSPoint topLeft = NSZeroPoint;
394 NSWindow *keyWin = [NSApp keyWindow];
395 NSWindow *win = [windowController window];
399 // If there is a key window, cascade from it, otherwise use the autosaved
400 // window position (if any).
402 NSRect frame = [keyWin frame];
403 topLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
405 NSString *topLeftString = [[NSUserDefaults standardUserDefaults]
406 stringForKey:MMTopLeftPointKey];
408 topLeft = NSPointFromString(topLeftString);
411 if (!NSEqualPoints(topLeft, NSZeroPoint)) {
413 topLeft = [win cascadeTopLeftFromPoint:topLeft];
415 [win setFrameTopLeftPoint:topLeft];
418 if (openSelectionString) {
419 // There is some text to paste into this window as a result of the
420 // services menu "Open selection ..." being used.
421 [[windowController vimController] dropString:openSelectionString];
422 [openSelectionString release];
423 openSelectionString = nil;
427 - (IBAction)newWindow:(id)sender
429 [self launchVimProcessWithArguments:nil];
432 - (IBAction)fileOpen:(id)sender
434 NSOpenPanel *panel = [NSOpenPanel openPanel];
435 [panel setAllowsMultipleSelection:YES];
437 int result = [panel runModalForTypes:nil];
438 if (NSOKButton == result)
439 [self application:NSApp openFiles:[panel filenames]];
442 - (IBAction)selectNextWindow:(id)sender
444 unsigned i, count = [vimControllers count];
447 NSWindow *keyWindow = [NSApp keyWindow];
448 for (i = 0; i < count; ++i) {
449 MMVimController *vc = [vimControllers objectAtIndex:i];
450 if ([[[vc windowController] window] isEqual:keyWindow])
457 MMVimController *vc = [vimControllers objectAtIndex:i];
458 [[vc windowController] showWindow:self];
462 - (IBAction)selectPreviousWindow:(id)sender
464 unsigned i, count = [vimControllers count];
467 NSWindow *keyWindow = [NSApp keyWindow];
468 for (i = 0; i < count; ++i) {
469 MMVimController *vc = [vimControllers objectAtIndex:i];
470 if ([[[vc windowController] window] isEqual:keyWindow])
480 MMVimController *vc = [vimControllers objectAtIndex:i];
481 [[vc windowController] showWindow:self];
485 - (IBAction)fontSizeUp:(id)sender
487 [[NSFontManager sharedFontManager] modifyFont:
488 [NSNumber numberWithInt:NSSizeUpFontAction]];
491 - (IBAction)fontSizeDown:(id)sender
493 [[NSFontManager sharedFontManager] modifyFont:
494 [NSNumber numberWithInt:NSSizeDownFontAction]];
497 - (byref id <MMFrontendProtocol>)
498 connectBackend:(byref in id <MMBackendProtocol>)backend
501 MMVimController *vc = nil;
502 //NSLog(@"Connect backend (pid=%d)", pid);
505 [(NSDistantObject*)backend
506 setProtocolForProxy:@protocol(MMBackendProtocol)];
508 vc = [[[MMVimController alloc]
509 initWithBackend:backend pid:pid] autorelease];
511 if (![vimControllers count]) {
512 // The first window autosaves its position. (The autosaving
513 // features of Cocoa are not used because we need more control over
514 // what is autosaved and when it is restored.)
515 [[vc windowController] setWindowAutosaveKey:MMTopLeftPointKey];
518 [vimControllers addObject:vc];
520 // HACK! MacVim does not get activated if it is launched from the
521 // terminal, so we forcibly activate here unless it is an untitled
522 // window opening (i.e. MacVim was opened from the Finder). Untitled
523 // windows are treated differently, else MacVim would steal the focus
524 // if another app was activated while the untitled window was loading.
525 if (!untitledWindowOpening)
526 [NSApp activateIgnoringOtherApps:YES];
528 untitledWindowOpening = NO;
530 // Arguments to a new Vim process that cannot be passed on the command
531 // line are stored in a dictionary and passed to the Vim process here.
532 NSNumber *key = [NSNumber numberWithInt:pid];
533 NSDictionary *args = [pidArguments objectForKey:key];
535 if ([args objectForKey:@"remoteID"]) {
536 [vc odbEdit:[args objectForKey:@"filenames"]
537 server:[[args objectForKey:@"remoteID"] unsignedIntValue]
538 path:[args objectForKey:@"remotePath"]
539 token:[args objectForKey:@"remoteToken"]];
542 if ([args objectForKey:@"selectionRangeData"]) {
543 MMSelectionRange *selRange = (MMSelectionRange*)
544 [[args objectForKey:@"selectionRangeData"] bytes];
545 [vc addVimInput:[self inputStringFromSelectionRange:selRange]];
548 [pidArguments removeObjectForKey:key];
554 @catch (NSException *e) {
555 NSLog(@"Exception caught in %s: \"%@\"", _cmd, e);
558 [vimControllers removeObject:vc];
564 - (NSArray *)serverList
566 NSMutableArray *array = [NSMutableArray array];
568 unsigned i, count = [vimControllers count];
569 for (i = 0; i < count; ++i) {
570 MMVimController *controller = [vimControllers objectAtIndex:i];
571 if ([controller serverName])
572 [array addObject:[controller serverName]];
578 @end // MMAppController
583 @implementation MMAppController (MMServices)
585 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
586 error:(NSString **)error
588 if (![[pboard types] containsObject:NSStringPboardType]) {
589 NSLog(@"WARNING: Pasteboard contains no object of type "
590 "NSStringPboardType");
594 MMVimController *vc = [self topmostVimController];
596 // Open a new tab first, since dropString: does not do this.
597 [vc sendMessage:AddNewTabMsgID data:nil];
598 [vc dropString:[pboard stringForType:NSStringPboardType]];
600 // NOTE: There is no window to paste the selection into, so save the
601 // text, open a new window, and paste the text when the next window
602 // opens. (If this is called several times in a row, then all but the
603 // last call might be ignored.)
604 if (openSelectionString) [openSelectionString release];
605 openSelectionString = [[pboard stringForType:NSStringPboardType] copy];
607 [self newWindow:self];
611 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
612 error:(NSString **)error
614 if (![[pboard types] containsObject:NSStringPboardType]) {
615 NSLog(@"WARNING: Pasteboard contains no object of type "
616 "NSStringPboardType");
620 // TODO: Parse multiple filenames and create array with names.
621 NSString *string = [pboard stringForType:NSStringPboardType];
622 string = [string stringByTrimmingCharactersInSet:
623 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
624 string = [string stringByStandardizingPath];
626 NSArray *filenames = [self filterFilesAndNotify:
627 [NSArray arrayWithObject:string]];
628 if ([filenames count] > 0) {
629 MMVimController *vc = nil;
630 if (userData && [userData isEqual:@"Tab"])
631 vc = [self topmostVimController];
634 [vc dropFiles:filenames forceOpen:YES];
636 [self application:NSApp openFiles:filenames];
641 @end // MMAppController (MMServices)
646 @implementation MMAppController (Private)
648 - (MMVimController *)keyVimController
650 NSWindow *keyWindow = [NSApp keyWindow];
652 unsigned i, count = [vimControllers count];
653 for (i = 0; i < count; ++i) {
654 MMVimController *vc = [vimControllers objectAtIndex:i];
655 if ([[[vc windowController] window] isEqual:keyWindow])
663 - (MMVimController *)topmostVimController
665 NSArray *windows = [NSApp orderedWindows];
666 if ([windows count] > 0) {
667 NSWindow *window = [windows objectAtIndex:0];
668 unsigned i, count = [vimControllers count];
669 for (i = 0; i < count; ++i) {
670 MMVimController *vc = [vimControllers objectAtIndex:i];
671 if ([[[vc windowController] window] isEqual:window])
679 - (int)launchVimProcessWithArguments:(NSArray *)args
681 NSString *taskPath = nil;
682 NSArray *taskArgs = nil;
683 NSString *path = [[NSBundle mainBundle] pathForAuxiliaryExecutable:@"Vim"];
686 NSLog(@"ERROR: Vim executable could not be found inside app bundle!");
690 if ([[NSUserDefaults standardUserDefaults] boolForKey:MMLoginShellKey]) {
691 // Run process with a login shell
692 // $SHELL -l -c "exec Vim -g -f args"
693 // (-g for GUI, -f for foreground, i.e. don't fork)
695 NSMutableString *execArg = [NSMutableString
696 stringWithFormat:@"exec \"%@\" -g -f", path];
698 // Append all arguments while making sure that arguments containing
699 // spaces are enclosed in quotes.
700 NSCharacterSet *space = [NSCharacterSet whitespaceCharacterSet];
701 unsigned i, count = [args count];
703 for (i = 0; i < count; ++i) {
704 NSString *arg = [args objectAtIndex:i];
705 if (NSNotFound != [arg rangeOfCharacterFromSet:space].location)
706 [execArg appendFormat:@" \"%@\"", arg];
708 [execArg appendFormat:@" %@", arg];
712 // Launch the process with a login shell so that users environment
713 // settings get sourced. This does not always happen when MacVim is
715 taskArgs = [NSArray arrayWithObjects:@"-l", @"-c", execArg, nil];
716 taskPath = [[[NSProcessInfo processInfo] environment]
717 objectForKey:@"SHELL"];
719 taskPath = @"/bin/sh";
721 // Run process directly:
723 // (-g for GUI, -f for foreground, i.e. don't fork)
725 taskArgs = [NSArray arrayWithObjects:@"-g", @"-f", nil];
727 taskArgs = [taskArgs arrayByAddingObjectsFromArray:args];
730 NSTask *task =[NSTask launchedTaskWithLaunchPath:taskPath
732 //NSLog(@"launch %@ with args=%@ (pid=%d)", taskPath, taskArgs,
733 // [task processIdentifier]);
735 return [task processIdentifier];
738 - (NSArray *)filterFilesAndNotify:(NSArray *)filenames
740 // Go trough 'filenames' array and make sure each file exists. Present
741 // warning dialog if some file was missing.
743 NSString *firstMissingFile = nil;
744 NSMutableArray *files = [NSMutableArray array];
745 unsigned i, count = [filenames count];
747 for (i = 0; i < count; ++i) {
748 NSString *name = [filenames objectAtIndex:i];
749 if ([[NSFileManager defaultManager] fileExistsAtPath:name]) {
750 [files addObject:name];
751 } else if (!firstMissingFile) {
752 firstMissingFile = name;
756 if (firstMissingFile) {
757 NSAlert *alert = [[NSAlert alloc] init];
758 [alert addButtonWithTitle:@"OK"];
761 if ([files count] >= count-1) {
762 [alert setMessageText:@"File not found"];
763 text = [NSString stringWithFormat:@"Could not open file with "
764 "name %@.", firstMissingFile];
766 [alert setMessageText:@"Multiple files not found"];
767 text = [NSString stringWithFormat:@"Could not open file with "
768 "name %@, and %d other files.", firstMissingFile,
769 count-[files count]-1];
772 [alert setInformativeText:text];
773 [alert setAlertStyle:NSWarningAlertStyle];
778 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
784 - (NSArray *)filterOpenFiles:(NSArray *)filenames remote:(OSType)theID
785 path:(NSString *)path
786 token:(NSAppleEventDescriptor *)token
787 selectionRange:(MMSelectionRange *)selRange
789 // Check if any of the files in the 'filenames' array are open in any Vim
790 // process. Remove the files that are open from the 'filenames' array and
791 // return it. If all files were filtered out, then raise the first file in
792 // the Vim process it is open. Files that are filtered are sent an odb
793 // open event in case theID is not zero.
795 MMVimController *raiseController = nil;
796 NSString *raiseFile = nil;
797 NSMutableArray *files = [filenames mutableCopy];
798 NSString *expr = [NSString stringWithFormat:
799 @"map([\"%@\"],\"bufloaded(v:val)\")",
800 [files componentsJoinedByString:@"\",\""]];
801 unsigned i, count = [vimControllers count];
803 for (i = 0; i < count && [files count]; ++i) {
804 MMVimController *controller = [vimControllers objectAtIndex:i];
805 id proxy = [controller backendProxy];
808 NSString *eval = [proxy evaluateExpression:expr];
809 NSIndexSet *idxSet = [NSIndexSet indexSetWithVimList:eval];
810 if ([idxSet count]) {
812 // Remember the file and which Vim that has it open so that
813 // we can raise it later on.
814 raiseController = controller;
815 raiseFile = [files objectAtIndex:[idxSet firstIndex]];
816 [[raiseFile retain] autorelease];
819 // Send an odb open event to the Vim process.
821 [controller odbEdit:[files objectsAtIndexes:idxSet]
822 server:theID path:path token:token];
824 // Remove all the files that were open in this Vim process and
825 // create a new expression to evaluate.
826 [files removeObjectsAtIndexes:idxSet];
827 expr = [NSString stringWithFormat:
828 @"map([\"%@\"],\"bufloaded(v:val)\")",
829 [files componentsJoinedByString:@"\",\""]];
832 @catch (NSException *e) {
837 if (![files count] && raiseFile) {
838 // Raise the window containing the first file that was already open,
839 // and make sure that the tab containing that file is selected. Only
840 // do this if there are no more files to open, otherwise sometimes the
841 // window with 'raiseFile' will be raised, other times it might be the
842 // window that will open with the files in the 'files' array.
843 raiseFile = [raiseFile stringByEscapingSpecialFilenameCharacters];
844 NSString *input = [NSString stringWithFormat:@"<C-\\><C-N>"
845 ":let oldswb=&swb|let &swb=\"useopen,usetab\"|"
846 "tab sb %@|let &swb=oldswb|unl oldswb|"
847 "cal foreground()|redr|f<CR>", raiseFile];
850 input = [input stringByAppendingString:
851 [self inputStringFromSelectionRange:selRange]];
853 [raiseController addVimInput:input];
859 - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
860 replyEvent:(NSAppleEventDescriptor *)reply
863 // Xcode sends this event to query MacVim which open files have been
865 NSLog(@"reply:%@", reply);
866 NSLog(@"event:%@", event);
868 NSEnumerator *e = [vimControllers objectEnumerator];
870 while ((vc = [e nextObject])) {
871 DescType type = [reply descriptorType];
872 unsigned len = [[type data] length];
873 NSMutableData *data = [NSMutableData data];
875 [data appendBytes:&type length:sizeof(DescType)];
876 [data appendBytes:&len length:sizeof(unsigned)];
877 [data appendBytes:[reply data] length:len];
879 [vc sendMessage:XcodeModMsgID data:data];
884 - (NSString *)inputStringFromSelectionRange:(MMSelectionRange *)selRange
887 return [NSString string];
890 if (selRange->lineNum < 0) {
891 input = [NSString stringWithFormat:@"<C-\\><C-N>%dGV%dG",
892 selRange->endRange+1, selRange->startRange+1];
894 input = [NSString stringWithFormat:@"<C-\\><C-N>%dGz.",
895 selRange->lineNum+1];
901 @end // MMAppController (Private)
906 @implementation NSMenu (MMExtras)
908 - (void)recurseSetAutoenablesItems:(BOOL)on
910 [self setAutoenablesItems:on];
912 int i, count = [self numberOfItems];
913 for (i = 0; i < count; ++i) {
914 NSMenuItem *item = [self itemAtIndex:i];
915 [item setEnabled:YES];
916 NSMenu *submenu = [item submenu];
918 [submenu recurseSetAutoenablesItems:on];
923 @end // NSMenu (MMExtras)
928 @implementation NSNumber (MMExtras)
931 return [self intValue];
933 @end // NSNumber (MMExtras)