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 and takes care of the main menu.
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
28 * The main menu is handled as follows. Each Vim controller keeps its own main
29 * menu. All menus except the "MacVim" menu are controlled by the Vim process.
30 * The app controller also keeps a reference to the "default main menu" which
31 * is set up in MainMenu.nib. When no editor window is open the default main
32 * menu is used. When a new editor window becomes main its main menu becomes
33 * the new main menu, this is done in -[MMAppController setMainMenu:].
34 * NOTE: Certain heuristics are used to find the "MacVim", "Windows", "File",
35 * and "Services" menu. If MainMenu.nib changes these heuristics may have to
36 * change as well. For specifics see the find... methods defined in the NSMenu
37 * category "MMExtras".
40 #import "MMAppController.h"
41 #import "MMPreferenceController.h"
42 #import "MMVimController.h"
43 #import "MMWindowController.h"
44 #import "Miscellaneous.h"
46 #ifdef MM_ENABLE_PLUGINS
47 #import "MMPlugInManager.h"
51 #import <CoreServices/CoreServices.h>
54 #define MM_HANDLE_XCODE_MOD_EVENT 0
58 // Default timeout intervals on all connections.
59 static NSTimeInterval MMRequestTimeout = 5;
60 static NSTimeInterval MMReplyTimeout = 5;
62 static NSString *MMWebsiteString = @"http://code.google.com/p/macvim/";
64 #if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4)
65 // Latency (in s) between FS event occuring and being reported to MacVim.
66 // Should be small so that MacVim is notified of changes to the ~/.vim
67 // directory more or less immediately.
68 static CFTimeInterval MMEventStreamLatency = 0.1;
72 #pragma options align=mac68k
75 short unused1; // 0 (not used)
76 short lineNum; // line to select (< 0 to specify range)
77 long startRange; // start of selection range (if line < 0)
78 long endRange; // end of selection range (if line < 0)
79 long unused2; // 0 (not used)
80 long theDate; // modification date/time
82 #pragma options align=reset
86 @interface MMAppController (MMServices)
87 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
88 error:(NSString **)error;
89 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
90 error:(NSString **)error;
91 - (void)newFileHere:(NSPasteboard *)pboard userData:(NSString *)userData
92 error:(NSString **)error;
96 @interface MMAppController (Private)
97 - (MMVimController *)topmostVimController;
98 - (int)launchVimProcessWithArguments:(NSArray *)args;
99 - (NSArray *)filterFilesAndNotify:(NSArray *)files;
100 - (NSArray *)filterOpenFiles:(NSArray *)filenames
101 openFilesDict:(NSDictionary **)openFiles;
102 #if MM_HANDLE_XCODE_MOD_EVENT
103 - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
104 replyEvent:(NSAppleEventDescriptor *)reply;
106 - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
107 replyEvent:(NSAppleEventDescriptor *)reply;
108 - (int)findLaunchingProcessWithoutArguments;
109 - (MMVimController *)findUnusedEditor;
110 - (NSMutableDictionary *)extractArgumentsFromOdocEvent:
111 (NSAppleEventDescriptor *)desc;
112 - (void)scheduleVimControllerPreloadAfterDelay:(NSTimeInterval)delay;
113 - (void)cancelVimControllerPreloadRequests;
114 - (void)preloadVimController:(id)sender;
115 - (int)maxPreloadCacheSize;
116 - (MMVimController *)takeVimControllerFromCache;
117 - (void)clearPreloadCacheWithCount:(int)count;
118 - (void)rebuildPreloadCache;
119 - (NSDate *)rcFilesModificationDate;
120 - (BOOL)openVimControllerWithArguments:(NSDictionary *)arguments;
121 - (void)activateWhenNextWindowOpens;
122 - (void)startWatchingVimDir;
123 - (void)stopWatchingVimDir;
124 - (void)handleFSEvent;
125 - (void)loadDefaultFont;
126 - (int)executeInLoginShell:(NSString *)path arguments:(NSArray *)args;
127 - (void)reapChildProcesses:(id)sender;
128 - (void)processInputQueues:(id)sender;
129 - (void)addVimController:(MMVimController *)vc;
131 #ifdef MM_ENABLE_PLUGINS
132 - (void)removePlugInMenu;
133 - (void)addPlugInMenuToMenu:(NSMenu *)mainMenu;
139 #if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4)
141 fsEventCallback(ConstFSEventStreamRef streamRef,
142 void *clientCallBackInfo,
145 const FSEventStreamEventFlags eventFlags[],
146 const FSEventStreamEventId eventIds[])
148 [[MMAppController sharedInstance] handleFSEvent];
152 @implementation MMAppController
156 static BOOL initDone = NO;
157 if (initDone) return;
162 NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
163 [NSNumber numberWithBool:NO], MMNoWindowKey,
164 [NSNumber numberWithInt:64], MMTabMinWidthKey,
165 [NSNumber numberWithInt:6*64], MMTabMaxWidthKey,
166 [NSNumber numberWithInt:132], MMTabOptimumWidthKey,
167 [NSNumber numberWithBool:YES], MMShowAddTabButtonKey,
168 [NSNumber numberWithInt:2], MMTextInsetLeftKey,
169 [NSNumber numberWithInt:1], MMTextInsetRightKey,
170 [NSNumber numberWithInt:1], MMTextInsetTopKey,
171 [NSNumber numberWithInt:1], MMTextInsetBottomKey,
172 @"MMTypesetter", MMTypesetterKey,
173 [NSNumber numberWithFloat:1], MMCellWidthMultiplierKey,
174 [NSNumber numberWithFloat:-1], MMBaselineOffsetKey,
175 [NSNumber numberWithBool:YES], MMTranslateCtrlClickKey,
176 [NSNumber numberWithInt:0], MMOpenInCurrentWindowKey,
177 [NSNumber numberWithBool:NO], MMNoFontSubstitutionKey,
178 [NSNumber numberWithBool:YES], MMLoginShellKey,
179 [NSNumber numberWithBool:NO], MMAtsuiRendererKey,
180 [NSNumber numberWithInt:MMUntitledWindowAlways],
182 [NSNumber numberWithBool:NO], MMTexturedWindowKey,
183 [NSNumber numberWithBool:NO], MMZoomBothKey,
184 @"", MMLoginShellCommandKey,
185 @"", MMLoginShellArgumentKey,
186 [NSNumber numberWithBool:YES], MMDialogsTrackPwdKey,
187 #ifdef MM_ENABLE_PLUGINS
188 [NSNumber numberWithBool:YES], MMShowLeftPlugInContainerKey,
190 [NSNumber numberWithInt:3], MMOpenLayoutKey,
191 [NSNumber numberWithBool:NO], MMVerticalSplitKey,
192 [NSNumber numberWithInt:0], MMPreloadCacheSizeKey,
193 [NSNumber numberWithInt:0], MMLastWindowClosedBehaviorKey,
194 [NSNumber numberWithBool:YES], MMLoadDefaultFontKey,
197 [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
199 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
200 [NSApp registerServicesMenuSendTypes:types returnTypes:types];
202 // NOTE: Set the current directory to user's home directory, otherwise it
203 // will default to the root directory. (This matters since new Vim
204 // processes inherit MacVim's environment variables.)
205 [[NSFileManager defaultManager] changeCurrentDirectoryPath:
211 if (!(self = [super init])) return nil;
213 [self loadDefaultFont];
215 vimControllers = [NSMutableArray new];
216 cachedVimControllers = [NSMutableArray new];
218 pidArguments = [NSMutableDictionary new];
219 inputQueues = [NSMutableDictionary new];
221 #ifdef MM_ENABLE_PLUGINS
222 NSString *plugInTitle = NSLocalizedString(@"Plug-In",
223 @"Plug-In menu title");
224 plugInMenuItem = [[NSMenuItem alloc] initWithTitle:plugInTitle
227 NSMenu *submenu = [[NSMenu alloc] initWithTitle:plugInTitle];
228 [plugInMenuItem setSubmenu:submenu];
232 // NOTE: Do not use the default connection since the Logitech Control
233 // Center (LCC) input manager steals and this would cause MacVim to
234 // never open any windows. (This is a bug in LCC but since they are
235 // unlikely to fix it, we graciously give them the default connection.)
236 connection = [[NSConnection alloc] initWithReceivePort:[NSPort port]
238 [connection setRootObject:self];
239 [connection setRequestTimeout:MMRequestTimeout];
240 [connection setReplyTimeout:MMReplyTimeout];
242 // NOTE! If the name of the connection changes here it must also be
243 // updated in MMBackend.m.
244 NSString *name = [NSString stringWithFormat:@"%@-connection",
245 [[NSBundle mainBundle] bundlePath]];
246 if (![connection registerName:name]) {
247 ASLogCrit(@"Failed to register connection with name '%@'", name);
248 [connection release]; connection = nil;
258 [connection release]; connection = nil;
259 [inputQueues release]; inputQueues = nil;
260 [pidArguments release]; pidArguments = nil;
261 [vimControllers release]; vimControllers = nil;
262 [cachedVimControllers release]; cachedVimControllers = nil;
263 [openSelectionString release]; openSelectionString = nil;
264 [recentFilesMenuItem release]; recentFilesMenuItem = nil;
265 [defaultMainMenu release]; defaultMainMenu = nil;
266 #ifdef MM_ENABLE_PLUGINS
267 [plugInMenuItem release]; plugInMenuItem = nil;
269 [appMenuItemTemplate release]; appMenuItemTemplate = nil;
274 - (void)applicationWillFinishLaunching:(NSNotification *)notification
276 // Remember the default menu so that it can be restored if the user closes
277 // all editor windows.
278 defaultMainMenu = [[NSApp mainMenu] retain];
280 // Store a copy of the default app menu so we can use this as a template
281 // for all other menus. We make a copy here because the "Services" menu
282 // will not yet have been populated at this time. If we don't we get
283 // problems trying to set key equivalents later on because they might clash
284 // with items on the "Services" menu.
285 appMenuItemTemplate = [defaultMainMenu itemAtIndex:0];
286 appMenuItemTemplate = [appMenuItemTemplate copy];
288 // Set up the "Open Recent" menu. See
289 // http://lapcatsoftware.com/blog/2007/07/10/
290 // working-without-a-nib-part-5-open-recent-menu/
292 // http://www.cocoabuilder.com/archive/message/cocoa/2007/8/15/187793
293 // for more information.
295 // The menu itself is created in MainMenu.nib but we still seem to have to
296 // hack around a bit to get it to work. (This has to be done in
297 // applicationWillFinishLaunching at the latest, otherwise it doesn't
299 NSMenu *fileMenu = [defaultMainMenu findFileMenu];
301 int idx = [fileMenu indexOfItemWithAction:@selector(fileOpen:)];
302 if (idx >= 0 && idx+1 < [fileMenu numberOfItems])
304 recentFilesMenuItem = [fileMenu itemWithTitle:@"Open Recent"];
305 [[recentFilesMenuItem submenu] performSelector:@selector(_setMenuName:)
306 withObject:@"NSRecentDocumentsMenu"];
308 // Note: The "Recent Files" menu must be moved around since there is no
309 // -[NSApp setRecentFilesMenu:] method. We keep a reference to it to
310 // facilitate this move (see setMainMenu: below).
311 [recentFilesMenuItem retain];
314 #if MM_HANDLE_XCODE_MOD_EVENT
315 [[NSAppleEventManager sharedAppleEventManager]
317 andSelector:@selector(handleXcodeModEvent:replyEvent:)
322 // Register 'mvim://' URL handler
323 [[NSAppleEventManager sharedAppleEventManager]
325 andSelector:@selector(handleGetURLEvent:replyEvent:)
326 forEventClass:kInternetEventClass
327 andEventID:kAEGetURL];
330 - (void)applicationDidFinishLaunching:(NSNotification *)notification
332 [NSApp setServicesProvider:self];
333 #ifdef MM_ENABLE_PLUGINS
334 [[MMPlugInManager sharedManager] loadAllPlugIns];
337 if ([self maxPreloadCacheSize] > 0) {
338 [self scheduleVimControllerPreloadAfterDelay:2];
339 [self startWatchingVimDir];
342 ASLogInfo(@"MacVim finished launching");
345 - (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
347 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
348 NSAppleEventManager *aem = [NSAppleEventManager sharedAppleEventManager];
349 NSAppleEventDescriptor *desc = [aem currentAppleEvent];
351 // The user default MMUntitledWindow can be set to control whether an
352 // untitled window should open on 'Open' and 'Reopen' events.
353 int untitledWindowFlag = [ud integerForKey:MMUntitledWindowKey];
355 BOOL isAppOpenEvent = [desc eventID] == kAEOpenApplication;
356 if (isAppOpenEvent && (untitledWindowFlag & MMUntitledWindowOnOpen) == 0)
359 BOOL isAppReopenEvent = [desc eventID] == kAEReopenApplication;
361 && (untitledWindowFlag & MMUntitledWindowOnReopen) == 0)
364 // When a process is started from the command line, the 'Open' event may
365 // contain a parameter to surpress the opening of an untitled window.
366 desc = [desc paramDescriptorForKeyword:keyAEPropData];
367 desc = [desc paramDescriptorForKeyword:keyMMUntitledWindow];
368 if (desc && ![desc booleanValue])
371 // Never open an untitled window if there is at least one open window or if
372 // there are processes that are currently launching.
373 if ([vimControllers count] > 0 || [pidArguments count] > 0)
376 // NOTE! This way it possible to start the app with the command-line
377 // argument '-nowindow yes' and no window will be opened by default but
378 // this argument will only be heeded when the application is opening.
379 if (isAppOpenEvent && [ud boolForKey:MMNoWindowKey] == YES)
385 - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
387 ASLogDebug(@"Opening untitled window...");
388 [self newWindow:self];
392 - (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
394 ASLogInfo(@"Opening files %@", filenames);
396 // Extract ODB/Xcode/Spotlight parameters from the current Apple event,
397 // sort the filenames, and then let openFiles:withArguments: do the heavy
400 if (!(filenames && [filenames count] > 0))
403 // Sort filenames since the Finder doesn't take care in preserving the
404 // order in which files are selected anyway (and "sorted" is more
405 // predictable than "random").
406 if ([filenames count] > 1)
407 filenames = [filenames sortedArrayUsingSelector:
408 @selector(localizedCompare:)];
410 // Extract ODB/Xcode/Spotlight parameters from the current Apple event
411 NSMutableDictionary *arguments = [self extractArgumentsFromOdocEvent:
412 [[NSAppleEventManager sharedAppleEventManager] currentAppleEvent]];
414 if ([self openFiles:filenames withArguments:arguments]) {
415 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
417 // TODO: Notify user of failure?
418 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
422 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
424 return (MMTerminateWhenLastWindowClosed ==
425 [[NSUserDefaults standardUserDefaults]
426 integerForKey:MMLastWindowClosedBehaviorKey]);
429 - (NSApplicationTerminateReply)applicationShouldTerminate:
430 (NSApplication *)sender
432 // TODO: Follow Apple's guidelines for 'Graceful Application Termination'
433 // (in particular, allow user to review changes and save).
434 int reply = NSTerminateNow;
435 BOOL modifiedBuffers = NO;
437 // Go through windows, checking for modified buffers. (Each Vim process
438 // tells MacVim when any buffer has been modified and MacVim sets the
439 // 'documentEdited' flag of the window correspondingly.)
440 NSEnumerator *e = [[NSApp windows] objectEnumerator];
442 while ((window = [e nextObject])) {
443 if ([window isDocumentEdited]) {
444 modifiedBuffers = YES;
449 if (modifiedBuffers) {
450 NSAlert *alert = [[NSAlert alloc] init];
451 [alert setAlertStyle:NSWarningAlertStyle];
452 [alert addButtonWithTitle:NSLocalizedString(@"Quit",
454 [alert addButtonWithTitle:NSLocalizedString(@"Cancel",
456 [alert setMessageText:NSLocalizedString(@"Quit without saving?",
457 @"Quit dialog with changed buffers, title")];
458 [alert setInformativeText:NSLocalizedString(
459 @"There are modified buffers, "
460 "if you quit now all changes will be lost. Quit anyway?",
461 @"Quit dialog with changed buffers, text")];
463 if ([alert runModal] != NSAlertFirstButtonReturn)
464 reply = NSTerminateCancel;
468 // No unmodified buffers, but give a warning if there are multiple
469 // windows and/or tabs open.
470 int numWindows = [vimControllers count];
473 // Count the number of open tabs
474 e = [vimControllers objectEnumerator];
476 while ((vc = [e nextObject]))
477 numTabs += [[vc objectForVimStateKey:@"numTabs"] intValue];
479 if (numWindows > 1 || numTabs > 1) {
480 NSAlert *alert = [[NSAlert alloc] init];
481 [alert setAlertStyle:NSWarningAlertStyle];
482 [alert addButtonWithTitle:NSLocalizedString(@"Quit",
484 [alert addButtonWithTitle:NSLocalizedString(@"Cancel",
486 [alert setMessageText:NSLocalizedString(
487 @"Are you sure you want to quit MacVim?",
488 @"Quit dialog with no changed buffers, title")];
490 NSString *info = nil;
491 if (numWindows > 1) {
492 if (numTabs > numWindows)
493 info = [NSString stringWithFormat:NSLocalizedString(
494 @"There are %d windows open in MacVim, with a "
495 "total of %d tabs. Do you want to quit anyway?",
496 @"Quit dialog with no changed buffers, text"),
497 numWindows, numTabs];
499 info = [NSString stringWithFormat:NSLocalizedString(
500 @"There are %d windows open in MacVim. "
501 "Do you want to quit anyway?",
502 @"Quit dialog with no changed buffers, text"),
506 info = [NSString stringWithFormat:NSLocalizedString(
507 @"There are %d tabs open in MacVim. "
508 "Do you want to quit anyway?",
509 @"Quit dialog with no changed buffers, text"),
513 [alert setInformativeText:info];
515 if ([alert runModal] != NSAlertFirstButtonReturn)
516 reply = NSTerminateCancel;
523 // Tell all Vim processes to terminate now (otherwise they'll leave swap
525 if (NSTerminateNow == reply) {
526 e = [vimControllers objectEnumerator];
528 while ((vc = [e nextObject])) {
529 ASLogDebug(@"Terminate pid=%d", [vc pid]);
530 [vc sendMessage:TerminateNowMsgID data:nil];
533 e = [cachedVimControllers objectEnumerator];
534 while ((vc = [e nextObject])) {
535 ASLogDebug(@"Terminate pid=%d (cached)", [vc pid]);
536 [vc sendMessage:TerminateNowMsgID data:nil];
539 // If a Vim process is being preloaded as we quit we have to forcibly
540 // kill it since we have not established a connection yet.
541 if (preloadPid > 0) {
542 ASLogDebug(@"Kill incomplete preloaded process pid=%d", preloadPid);
543 kill(preloadPid, SIGKILL);
546 // If a Vim process was loading as we quit we also have to kill it.
547 e = [[pidArguments allKeys] objectEnumerator];
549 while ((pidKey = [e nextObject])) {
550 ASLogDebug(@"Kill incomplete process pid=%d", [pidKey intValue]);
551 kill([pidKey intValue], SIGKILL);
554 // Sleep a little to allow all the Vim processes to exit.
561 - (void)applicationWillTerminate:(NSNotification *)notification
563 ASLogInfo(@"Terminating MacVim...");
565 [self stopWatchingVimDir];
567 #ifdef MM_ENABLE_PLUGINS
568 [[MMPlugInManager sharedManager] unloadAllPlugIns];
571 #if MM_HANDLE_XCODE_MOD_EVENT
572 [[NSAppleEventManager sharedAppleEventManager]
573 removeEventHandlerForEventClass:'KAHL'
577 // This will invalidate all connections (since they were spawned from this
579 [connection invalidate];
581 // Deactivate the font we loaded from the app bundle.
582 // NOTE: This can take quite a while (~500 ms), so termination will be
583 // noticeably faster if loading of the default font is disabled.
584 if (fontContainerRef) {
585 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
586 fontContainerRef = 0;
589 [NSApp setDelegate:nil];
591 // Try to wait for all child processes to avoid leaving zombies behind (but
592 // don't wait around for too long).
593 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:2];
594 while ([timeOutDate timeIntervalSinceNow] > 0) {
595 [self reapChildProcesses:nil];
596 if (numChildProcesses <= 0)
599 ASLogDebug(@"%d processes still left, hold on...", numChildProcesses);
601 // Run in NSConnectionReplyMode while waiting instead of calling e.g.
602 // usleep(). Otherwise incoming messages may clog up the DO queues and
603 // the outgoing TerminateNowMsgID sent earlier never reaches the Vim
605 // This has at least one side-effect, namely we may receive the
606 // annoying "dropping incoming DO message". (E.g. this may happen if
607 // you quickly hit Cmd-n several times in a row and then immediately
608 // press Cmd-q, Enter.)
609 while (CFRunLoopRunInMode((CFStringRef)NSConnectionReplyMode,
610 0.05, true) == kCFRunLoopRunHandledSource)
614 if (numChildProcesses > 0) {
615 ASLogNotice(@"%d zombies left behind", numChildProcesses);
619 + (MMAppController *)sharedInstance
621 // Note: The app controller is a singleton which is instantiated in
622 // MainMenu.nib where it is also connected as the delegate of NSApp.
623 id delegate = [NSApp delegate];
624 return [delegate isKindOfClass:self] ? (MMAppController*)delegate : nil;
627 - (NSMenu *)defaultMainMenu
629 return defaultMainMenu;
632 - (NSMenuItem *)appMenuItemTemplate
634 return appMenuItemTemplate;
637 - (void)removeVimController:(id)controller
639 ASLogDebug(@"Remove Vim controller pid=%d id=%d (processingFlag=%d)",
640 [controller pid], [controller identifier], processingFlag);
642 int idx = [vimControllers indexOfObject:controller];
643 if (NSNotFound == idx) {
644 ASLogDebug(@"Controller not found, probably due to duplicate removal");
648 [controller cleanup];
650 [vimControllers removeObjectAtIndex:idx];
652 if (![vimControllers count]) {
653 // The last editor window just closed so restore the main menu back to
654 // its default state (which is defined in MainMenu.nib).
655 [self setMainMenu:defaultMainMenu];
657 BOOL hide = (MMHideWhenLastWindowClosed ==
658 [[NSUserDefaults standardUserDefaults]
659 integerForKey:MMLastWindowClosedBehaviorKey]);
664 // There is a small delay before the Vim process actually exits so wait a
665 // little before trying to reap the child process. If the process still
666 // hasn't exited after this wait it won't be reaped until the next time
667 // reapChildProcesses: is called (but this should be harmless).
668 [self performSelector:@selector(reapChildProcesses:)
673 - (void)windowControllerWillOpen:(MMWindowController *)windowController
675 NSPoint topLeft = NSZeroPoint;
676 NSWindow *topWin = [[[self topmostVimController] windowController] window];
677 NSWindow *win = [windowController window];
681 // If there is a window belonging to a Vim process, cascade from it,
682 // otherwise use the autosaved window position (if any).
684 NSRect frame = [topWin frame];
685 topLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
687 NSString *topLeftString = [[NSUserDefaults standardUserDefaults]
688 stringForKey:MMTopLeftPointKey];
690 topLeft = NSPointFromString(topLeftString);
693 if (!NSEqualPoints(topLeft, NSZeroPoint)) {
694 NSPoint oldTopLeft = topLeft;
696 topLeft = [win cascadeTopLeftFromPoint:topLeft];
698 [win setFrameTopLeftPoint:topLeft];
701 NSPoint screenOrigin = [[win screen] frame].origin;
702 if ([win frame].origin.y < screenOrigin.y) {
703 // Try to avoid shifting the new window downwards if it means
704 // that the bottom of the window will be off the screen. E.g.
705 // if the user has set windows to open maximized in the
706 // vertical direction then the new window will cascade
707 // horizontally only.
708 topLeft.y = oldTopLeft.y;
709 [win setFrameTopLeftPoint:topLeft];
712 if ([win frame].origin.y < screenOrigin.y) {
713 // Move the window to the top of the screen if the bottom of
714 // the window is still obscured.
715 topLeft.y = NSMaxY([[win screen] frame]);
716 [win setFrameTopLeftPoint:topLeft];
719 ASLogNotice(@"Window not on screen, don't constrain position");
723 if (1 == [vimControllers count]) {
724 // The first window autosaves its position. (The autosaving
725 // features of Cocoa are not used because we need more control over
726 // what is autosaved and when it is restored.)
727 [windowController setWindowAutosaveKey:MMTopLeftPointKey];
730 if (openSelectionString) {
731 // TODO: Pass this as a parameter instead! Get rid of
732 // 'openSelectionString' etc.
734 // There is some text to paste into this window as a result of the
735 // services menu "Open selection ..." being used.
736 [[windowController vimController] dropString:openSelectionString];
737 [openSelectionString release];
738 openSelectionString = nil;
741 if (shouldActivateWhenNextWindowOpens) {
742 [NSApp activateIgnoringOtherApps:YES];
743 shouldActivateWhenNextWindowOpens = NO;
747 - (void)setMainMenu:(NSMenu *)mainMenu
749 if ([NSApp mainMenu] == mainMenu) return;
751 // If the new menu has a "Recent Files" dummy item, then swap the real item
752 // for the dummy. We are forced to do this since Cocoa initializes the
753 // "Recent Files" menu and there is no way to simply point Cocoa to a new
754 // item each time the menus are swapped.
755 NSMenu *fileMenu = [mainMenu findFileMenu];
756 if (recentFilesMenuItem && fileMenu) {
758 [fileMenu indexOfItemWithAction:@selector(recentFilesDummy:)];
760 NSMenuItem *dummyItem = [[fileMenu itemAtIndex:dummyIdx] retain];
761 [fileMenu removeItemAtIndex:dummyIdx];
763 NSMenu *recentFilesParentMenu = [recentFilesMenuItem menu];
764 int idx = [recentFilesParentMenu indexOfItem:recentFilesMenuItem];
766 [[recentFilesMenuItem retain] autorelease];
767 [recentFilesParentMenu removeItemAtIndex:idx];
768 [recentFilesParentMenu insertItem:dummyItem atIndex:idx];
771 [fileMenu insertItem:recentFilesMenuItem atIndex:dummyIdx];
776 // Now set the new menu. Notice that we keep one menu for each editor
777 // window since each editor can have its own set of menus. When swapping
778 // menus we have to tell Cocoa where the new "MacVim", "Windows", and
779 // "Services" menu are.
780 [NSApp setMainMenu:mainMenu];
782 // Setting the "MacVim" (or "Application") menu ensures that it is typeset
783 // in boldface. (The setAppleMenu: method used to be public but is now
784 // private so this will have to be considered a bit of a hack!)
785 NSMenu *appMenu = [mainMenu findApplicationMenu];
786 [NSApp performSelector:@selector(setAppleMenu:) withObject:appMenu];
788 NSMenu *servicesMenu = [mainMenu findServicesMenu];
789 [NSApp setServicesMenu:servicesMenu];
791 NSMenu *windowsMenu = [mainMenu findWindowsMenu];
793 // Cocoa isn't clever enough to get rid of items it has added to the
794 // "Windows" menu so we have to do it ourselves otherwise there will be
795 // multiple menu items for each window in the "Windows" menu.
796 // This code assumes that the only items Cocoa add are ones which
797 // send off the action makeKeyAndOrderFront:. (Cocoa will not add
798 // another separator item if the last item on the "Windows" menu
799 // already is a separator, so we needen't worry about separators.)
800 int i, count = [windowsMenu numberOfItems];
801 for (i = count-1; i >= 0; --i) {
802 NSMenuItem *item = [windowsMenu itemAtIndex:i];
803 if ([item action] == @selector(makeKeyAndOrderFront:))
804 [windowsMenu removeItem:item];
807 [NSApp setWindowsMenu:windowsMenu];
809 #ifdef MM_ENABLE_PLUGINS
810 // Move plugin menu from old to new main menu.
811 [self removePlugInMenu];
812 [self addPlugInMenuToMenu:mainMenu];
816 - (NSArray *)filterOpenFiles:(NSArray *)filenames
818 return [self filterOpenFiles:filenames openFilesDict:nil];
821 - (BOOL)openFiles:(NSArray *)filenames withArguments:(NSDictionary *)args
823 // Opening files works like this:
824 // a) filter out any already open files
825 // b) open any remaining files
827 // A file is opened in an untitled window if there is one (it may be
828 // currently launching, or it may already be visible), otherwise a new
831 // Each launching Vim process has a dictionary of arguments that are passed
832 // to the process when in checks in (via connectBackend:pid:). The
833 // arguments for each launching process can be looked up by its PID (in the
834 // pidArguments dictionary).
836 NSMutableDictionary *arguments = (args ? [[args mutableCopy] autorelease]
837 : [NSMutableDictionary dictionary]);
839 filenames = normalizeFilenames(filenames);
842 // a) Filter out any already open files
844 NSString *firstFile = [filenames objectAtIndex:0];
845 MMVimController *firstController = nil;
846 NSDictionary *openFilesDict = nil;
847 filenames = [self filterOpenFiles:filenames openFilesDict:&openFilesDict];
849 // Pass arguments to vim controllers that had files open.
851 NSEnumerator *e = [openFilesDict keyEnumerator];
853 // (Indicate that we do not wish to open any files at the moment.)
854 [arguments setObject:[NSNumber numberWithBool:YES] forKey:@"dontOpen"];
856 while ((key = [e nextObject])) {
857 NSArray *files = [openFilesDict objectForKey:key];
858 [arguments setObject:files forKey:@"filenames"];
860 MMVimController *vc = [key pointerValue];
861 [vc passArguments:arguments];
863 // If this controller holds the first file, then remember it for later.
864 if ([files containsObject:firstFile])
865 firstController = vc;
868 // The meaning of "layout" is defined by the WIN_* defines in main.c.
869 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
870 int layout = [ud integerForKey:MMOpenLayoutKey];
871 BOOL splitVert = [ud boolForKey:MMVerticalSplitKey];
872 BOOL openInCurrentWindow = [ud boolForKey:MMOpenInCurrentWindowKey];
874 if (splitVert && MMLayoutHorizontalSplit == layout)
875 layout = MMLayoutVerticalSplit;
876 if (layout < 0 || (layout > MMLayoutTabs && openInCurrentWindow))
877 layout = MMLayoutTabs;
879 if ([filenames count] == 0) {
880 // Raise the window containing the first file that was already open,
881 // and make sure that the tab containing that file is selected. Only
882 // do this when there are no more files to open, otherwise sometimes
883 // the window with 'firstFile' will be raised, other times it might be
884 // the window that will open with the files in the 'filenames' array.
885 firstFile = [firstFile stringByEscapingSpecialFilenameCharacters];
887 NSString *bufCmd = @"tab sb";
889 case MMLayoutHorizontalSplit: bufCmd = @"sb"; break;
890 case MMLayoutVerticalSplit: bufCmd = @"vert sb"; break;
891 case MMLayoutArglist: bufCmd = @"b"; break;
894 NSString *input = [NSString stringWithFormat:@"<C-\\><C-N>"
895 ":let oldswb=&swb|let &swb=\"useopen,usetab\"|"
896 "%@ %@|let &swb=oldswb|unl oldswb|"
897 "cal foreground()<CR>", bufCmd, firstFile];
899 [firstController addVimInput:input];
904 // Add filenames to "Recent Files" menu, unless they are being edited
905 // remotely (using ODB).
906 if ([arguments objectForKey:@"remoteID"] == nil) {
907 [[NSDocumentController sharedDocumentController]
908 noteNewRecentFilePaths:filenames];
912 // b) Open any remaining files
915 [arguments setObject:[NSNumber numberWithInt:layout] forKey:@"layout"];
916 [arguments setObject:filenames forKey:@"filenames"];
917 // (Indicate that files should be opened from now on.)
918 [arguments setObject:[NSNumber numberWithBool:NO] forKey:@"dontOpen"];
921 if (openInCurrentWindow && (vc = [self topmostVimController])) {
922 // Open files in an already open window.
923 [[[vc windowController] window] makeKeyAndOrderFront:self];
924 [vc passArguments:arguments];
929 int numFiles = [filenames count];
930 if (MMLayoutWindows == layout && numFiles > 1) {
931 // Open one file at a time in a new window, but don't open too many at
932 // once (at most cap+1 windows will open). If the user has increased
933 // the preload cache size we'll take that as a hint that more windows
934 // should be able to open at once.
935 int cap = [self maxPreloadCacheSize] - 1;
936 if (cap < 4) cap = 4;
937 if (cap > numFiles) cap = numFiles;
940 for (i = 0; i < cap; ++i) {
941 NSArray *a = [NSArray arrayWithObject:[filenames objectAtIndex:i]];
942 [arguments setObject:a forKey:@"filenames"];
944 // NOTE: We have to copy the args since we'll mutate them in the
945 // next loop and the below call may retain the arguments while
946 // waiting for a process to start.
947 NSDictionary *args = [[arguments copy] autorelease];
949 openOk = [self openVimControllerWithArguments:args];
953 // Open remaining files in tabs in a new window.
954 if (openOk && numFiles > cap) {
955 NSRange range = { i, numFiles-cap };
956 NSArray *a = [filenames subarrayWithRange:range];
957 [arguments setObject:a forKey:@"filenames"];
958 [arguments setObject:[NSNumber numberWithInt:MMLayoutTabs]
961 openOk = [self openVimControllerWithArguments:arguments];
964 // Open all files at once.
965 openOk = [self openVimControllerWithArguments:arguments];
971 #ifdef MM_ENABLE_PLUGINS
972 - (void)addItemToPlugInMenu:(NSMenuItem *)item
974 NSMenu *menu = [plugInMenuItem submenu];
976 if ([menu numberOfItems] == 1)
977 [self addPlugInMenuToMenu:[NSApp mainMenu]];
980 - (void)removeItemFromPlugInMenu:(NSMenuItem *)item
982 NSMenu *menu = [plugInMenuItem submenu];
983 [menu removeItem:item];
984 if ([menu numberOfItems] == 0)
985 [self removePlugInMenu];
989 - (IBAction)newWindow:(id)sender
991 ASLogDebug(@"Open new window");
993 // A cached controller requires no loading times and results in the new
994 // window popping up instantaneously. If the cache is empty it may take
995 // 1-2 seconds to start a new Vim process.
996 MMVimController *vc = [self takeVimControllerFromCache];
998 [[vc backendProxy] acknowledgeConnection];
1000 [self launchVimProcessWithArguments:nil];
1004 - (IBAction)newWindowAndActivate:(id)sender
1006 [self activateWhenNextWindowOpens];
1007 [self newWindow:sender];
1010 - (IBAction)fileOpen:(id)sender
1012 ASLogDebug(@"Show file open panel");
1014 NSString *dir = nil;
1015 BOOL trackPwd = [[NSUserDefaults standardUserDefaults]
1016 boolForKey:MMDialogsTrackPwdKey];
1018 MMVimController *vc = [self keyVimController];
1019 if (vc) dir = [vc objectForVimStateKey:@"pwd"];
1022 NSOpenPanel *panel = [NSOpenPanel openPanel];
1023 [panel setAllowsMultipleSelection:YES];
1024 [panel setAccessoryView:showHiddenFilesView()];
1026 int result = [panel runModalForDirectory:dir file:nil types:nil];
1027 if (NSOKButton == result)
1028 [self application:NSApp openFiles:[panel filenames]];
1031 - (IBAction)selectNextWindow:(id)sender
1033 ASLogDebug(@"Select next window");
1035 unsigned i, count = [vimControllers count];
1038 NSWindow *keyWindow = [NSApp keyWindow];
1039 for (i = 0; i < count; ++i) {
1040 MMVimController *vc = [vimControllers objectAtIndex:i];
1041 if ([[[vc windowController] window] isEqual:keyWindow])
1048 MMVimController *vc = [vimControllers objectAtIndex:i];
1049 [[vc windowController] showWindow:self];
1053 - (IBAction)selectPreviousWindow:(id)sender
1055 ASLogDebug(@"Select previous window");
1057 unsigned i, count = [vimControllers count];
1060 NSWindow *keyWindow = [NSApp keyWindow];
1061 for (i = 0; i < count; ++i) {
1062 MMVimController *vc = [vimControllers objectAtIndex:i];
1063 if ([[[vc windowController] window] isEqual:keyWindow])
1073 MMVimController *vc = [vimControllers objectAtIndex:i];
1074 [[vc windowController] showWindow:self];
1078 - (IBAction)orderFrontPreferencePanel:(id)sender
1080 ASLogDebug(@"Show preferences panel");
1081 [[MMPreferenceController sharedPrefsWindowController] showWindow:self];
1084 - (IBAction)openWebsite:(id)sender
1086 ASLogDebug(@"Open MacVim website");
1087 [[NSWorkspace sharedWorkspace] openURL:
1088 [NSURL URLWithString:MMWebsiteString]];
1091 - (IBAction)showVimHelp:(id)sender
1093 ASLogDebug(@"Open window with Vim help");
1094 // Open a new window with the help window maximized.
1095 [self launchVimProcessWithArguments:[NSArray arrayWithObjects:
1096 @"-c", @":h gui_mac", @"-c", @":res", nil]];
1099 - (IBAction)zoomAll:(id)sender
1101 ASLogDebug(@"Zoom all windows");
1102 [NSApp makeWindowsPerform:@selector(performZoom:) inOrder:YES];
1105 - (IBAction)atsuiButtonClicked:(id)sender
1107 ASLogDebug(@"Toggle ATSUI renderer");
1108 // This action is called when the user clicks the "use ATSUI renderer"
1109 // button in the advanced preferences pane.
1110 [self rebuildPreloadCache];
1113 - (IBAction)loginShellButtonClicked:(id)sender
1115 ASLogDebug(@"Toggle login shell option");
1116 // This action is called when the user clicks the "use login shell" button
1117 // in the advanced preferences pane.
1118 [self rebuildPreloadCache];
1121 - (IBAction)quickstartButtonClicked:(id)sender
1123 ASLogDebug(@"Toggle Quickstart option");
1124 if ([self maxPreloadCacheSize] > 0) {
1125 [self scheduleVimControllerPreloadAfterDelay:1.0];
1126 [self startWatchingVimDir];
1128 [self cancelVimControllerPreloadRequests];
1129 [self clearPreloadCacheWithCount:-1];
1130 [self stopWatchingVimDir];
1134 - (MMVimController *)keyVimController
1136 NSWindow *keyWindow = [NSApp keyWindow];
1138 unsigned i, count = [vimControllers count];
1139 for (i = 0; i < count; ++i) {
1140 MMVimController *vc = [vimControllers objectAtIndex:i];
1141 if ([[[vc windowController] window] isEqual:keyWindow])
1149 - (unsigned)connectBackend:(byref in id <MMBackendProtocol>)proxy pid:(int)pid
1151 ASLogDebug(@"pid=%d", pid);
1153 [(NSDistantObject*)proxy setProtocolForProxy:@protocol(MMBackendProtocol)];
1155 // NOTE: Allocate the vim controller now but don't add it to the list of
1156 // controllers since this is a distributed object call and as such can
1157 // arrive at unpredictable times (e.g. while iterating the list of vim
1159 // (What if input arrives before the vim controller is added to the list of
1160 // controllers? This should not be a problem since the input isn't
1161 // processed immediately (see processInput:forIdentifier:).)
1162 MMVimController *vc = [[MMVimController alloc] initWithBackend:proxy
1164 [self performSelector:@selector(addVimController:)
1170 return [vc identifier];
1173 - (oneway void)processInput:(in bycopy NSArray *)queue
1174 forIdentifier:(unsigned)identifier
1176 // NOTE: Input is not handled immediately since this is a distributed
1177 // object call and as such can arrive at unpredictable times. Instead,
1178 // queue the input and process it when the run loop is updated.
1180 if (!(queue && identifier)) {
1181 ASLogWarn(@"Bad input for identifier=%d", identifier);
1185 ASLogDebug(@"QUEUE for identifier=%d: <<< %@>>>", identifier,
1186 debugStringForMessageQueue(queue));
1188 NSNumber *key = [NSNumber numberWithUnsignedInt:identifier];
1189 NSArray *q = [inputQueues objectForKey:key];
1191 q = [q arrayByAddingObjectsFromArray:queue];
1192 [inputQueues setObject:q forKey:key];
1194 [inputQueues setObject:queue forKey:key];
1197 // NOTE: We must use "event tracking mode" as well as "default mode",
1198 // otherwise the input queue will not be processed e.g. during live
1200 [self performSelector:@selector(processInputQueues:)
1203 inModes:[NSArray arrayWithObjects:NSDefaultRunLoopMode,
1204 NSEventTrackingRunLoopMode, nil]];
1207 - (NSArray *)serverList
1209 NSMutableArray *array = [NSMutableArray array];
1211 unsigned i, count = [vimControllers count];
1212 for (i = 0; i < count; ++i) {
1213 MMVimController *controller = [vimControllers objectAtIndex:i];
1214 if ([controller serverName])
1215 [array addObject:[controller serverName]];
1221 @end // MMAppController
1226 @implementation MMAppController (MMServices)
1228 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
1229 error:(NSString **)error
1231 if (![[pboard types] containsObject:NSStringPboardType]) {
1232 ASLogNotice(@"Pasteboard contains no NSStringPboardType");
1236 ASLogInfo(@"Open new window containing current selection");
1238 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
1239 BOOL openInCurrentWindow = [ud boolForKey:MMOpenInCurrentWindowKey];
1240 MMVimController *vc;
1242 if (openInCurrentWindow && (vc = [self topmostVimController])) {
1243 [vc sendMessage:AddNewTabMsgID data:nil];
1244 [vc dropString:[pboard stringForType:NSStringPboardType]];
1246 // Save the text, open a new window, and paste the text when the next
1247 // window opens. (If this is called several times in a row, then all
1248 // but the last call may be ignored.)
1249 if (openSelectionString) [openSelectionString release];
1250 openSelectionString = [[pboard stringForType:NSStringPboardType] copy];
1252 [self newWindow:self];
1256 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
1257 error:(NSString **)error
1259 if (![[pboard types] containsObject:NSStringPboardType]) {
1260 ASLogNotice(@"Pasteboard contains no NSStringPboardType");
1264 // TODO: Parse multiple filenames and create array with names.
1265 NSString *string = [pboard stringForType:NSStringPboardType];
1266 string = [string stringByTrimmingCharactersInSet:
1267 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
1268 string = [string stringByStandardizingPath];
1270 ASLogInfo(@"Open new window with selected file: %@", string);
1272 NSArray *filenames = [self filterFilesAndNotify:
1273 [NSArray arrayWithObject:string]];
1274 if ([filenames count] == 0)
1277 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
1278 BOOL openInCurrentWindow = [ud boolForKey:MMOpenInCurrentWindowKey];
1279 MMVimController *vc;
1281 if (openInCurrentWindow && (vc = [self topmostVimController])) {
1282 [vc dropFiles:filenames forceOpen:YES];
1284 [self openFiles:filenames withArguments:nil];
1288 - (void)newFileHere:(NSPasteboard *)pboard userData:(NSString *)userData
1289 error:(NSString **)error
1291 if (![[pboard types] containsObject:NSStringPboardType]) {
1292 ASLogNotice(@"Pasteboard contains no NSStringPboardType");
1296 NSString *path = [pboard stringForType:NSStringPboardType];
1299 if (![[NSFileManager defaultManager] fileExistsAtPath:path
1300 isDirectory:&dirIndicator]) {
1301 ASLogNotice(@"Invalid path. Cannot open new document at: %@", path);
1305 ASLogInfo(@"Open new file at path=%@", path);
1308 path = [path stringByDeletingLastPathComponent];
1310 path = [path stringByEscapingSpecialFilenameCharacters];
1312 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
1313 BOOL openInCurrentWindow = [ud boolForKey:MMOpenInCurrentWindowKey];
1314 MMVimController *vc;
1316 if (openInCurrentWindow && (vc = [self topmostVimController])) {
1317 NSString *input = [NSString stringWithFormat:@"<C-\\><C-N>"
1318 ":tabe|cd %@<CR>", path];
1319 [vc addVimInput:input];
1321 NSString *input = [NSString stringWithFormat:@":cd %@", path];
1322 [self launchVimProcessWithArguments:[NSArray arrayWithObjects:
1323 @"-c", input, nil]];
1327 @end // MMAppController (MMServices)
1332 @implementation MMAppController (Private)
1334 - (MMVimController *)topmostVimController
1336 // Find the topmost visible window which has an associated vim controller.
1337 NSEnumerator *e = [[NSApp orderedWindows] objectEnumerator];
1339 while ((window = [e nextObject]) && [window isVisible]) {
1340 unsigned i, count = [vimControllers count];
1341 for (i = 0; i < count; ++i) {
1342 MMVimController *vc = [vimControllers objectAtIndex:i];
1343 if ([[[vc windowController] window] isEqual:window])
1351 - (int)launchVimProcessWithArguments:(NSArray *)args
1354 NSString *path = [[NSBundle mainBundle] pathForAuxiliaryExecutable:@"Vim"];
1357 ASLogCrit(@"Vim executable could not be found inside app bundle!");
1361 NSArray *taskArgs = [NSArray arrayWithObjects:@"-g", @"-f", nil];
1363 taskArgs = [taskArgs arrayByAddingObjectsFromArray:args];
1365 BOOL useLoginShell = [[NSUserDefaults standardUserDefaults]
1366 boolForKey:MMLoginShellKey];
1367 if (useLoginShell) {
1368 // Run process with a login shell, roughly:
1369 // echo "exec Vim -g -f args" | ARGV0=-`basename $SHELL` $SHELL [-l]
1370 pid = [self executeInLoginShell:path arguments:taskArgs];
1372 // Run process directly:
1374 NSTask *task = [NSTask launchedTaskWithLaunchPath:path
1375 arguments:taskArgs];
1376 pid = task ? [task processIdentifier] : -1;
1380 // The 'pidArguments' dictionary keeps arguments to be passed to the
1381 // process when it connects (this is in contrast to arguments which are
1382 // passed on the command line, like '-f' and '-g').
1383 // If this method is called with nil arguments we take this as a hint
1384 // that this is an "untitled window" being launched and add a null
1385 // object to the 'pidArguments' dictionary. This way we can detect if
1386 // an untitled window is being launched by looking for null objects in
1388 // If this method is called with non-nil arguments then it is assumed
1389 // that the caller takes care of adding items to 'pidArguments' as
1390 // necessary (only some arguments are passed on connect, e.g. files to
1393 [pidArguments setObject:[NSNull null]
1394 forKey:[NSNumber numberWithInt:pid]];
1396 ASLogWarn(@"Failed to launch Vim process: args=%@, useLoginShell=%d",
1397 args, useLoginShell);
1403 - (NSArray *)filterFilesAndNotify:(NSArray *)filenames
1405 // Go trough 'filenames' array and make sure each file exists. Present
1406 // warning dialog if some file was missing.
1408 NSString *firstMissingFile = nil;
1409 NSMutableArray *files = [NSMutableArray array];
1410 unsigned i, count = [filenames count];
1412 for (i = 0; i < count; ++i) {
1413 NSString *name = [filenames objectAtIndex:i];
1414 if ([[NSFileManager defaultManager] fileExistsAtPath:name]) {
1415 [files addObject:name];
1416 } else if (!firstMissingFile) {
1417 firstMissingFile = name;
1421 if (firstMissingFile) {
1422 NSAlert *alert = [[NSAlert alloc] init];
1423 [alert addButtonWithTitle:NSLocalizedString(@"OK",
1427 if ([files count] >= count-1) {
1428 [alert setMessageText:NSLocalizedString(@"File not found",
1429 @"File not found dialog, title")];
1430 text = [NSString stringWithFormat:NSLocalizedString(
1431 @"Could not open file with name %@.",
1432 @"File not found dialog, text"), firstMissingFile];
1434 [alert setMessageText:NSLocalizedString(@"Multiple files not found",
1435 @"File not found dialog, title")];
1436 text = [NSString stringWithFormat:NSLocalizedString(
1437 @"Could not open file with name %@, and %d other files.",
1438 @"File not found dialog, text"),
1439 firstMissingFile, count-[files count]-1];
1442 [alert setInformativeText:text];
1443 [alert setAlertStyle:NSWarningAlertStyle];
1448 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
1454 - (NSArray *)filterOpenFiles:(NSArray *)filenames
1455 openFilesDict:(NSDictionary **)openFiles
1457 // Filter out any files in the 'filenames' array that are open and return
1458 // all files that are not already open. On return, the 'openFiles'
1459 // parameter (if non-nil) will point to a dictionary of open files, indexed
1460 // by Vim controller.
1462 NSMutableDictionary *dict = [NSMutableDictionary dictionary];
1463 NSMutableArray *files = [filenames mutableCopy];
1465 // TODO: Escape special characters in 'files'?
1466 NSString *expr = [NSString stringWithFormat:
1467 @"map([\"%@\"],\"bufloaded(v:val)\")",
1468 [files componentsJoinedByString:@"\",\""]];
1470 unsigned i, count = [vimControllers count];
1471 for (i = 0; i < count && [files count] > 0; ++i) {
1472 MMVimController *vc = [vimControllers objectAtIndex:i];
1474 // Query Vim for which files in the 'files' array are open.
1475 NSString *eval = [vc evaluateVimExpression:expr];
1476 if (!eval) continue;
1478 NSIndexSet *idxSet = [NSIndexSet indexSetWithVimList:eval];
1479 if ([idxSet count] > 0) {
1480 [dict setObject:[files objectsAtIndexes:idxSet]
1481 forKey:[NSValue valueWithPointer:vc]];
1483 // Remove all the files that were open in this Vim process and
1484 // create a new expression to evaluate.
1485 [files removeObjectsAtIndexes:idxSet];
1486 expr = [NSString stringWithFormat:
1487 @"map([\"%@\"],\"bufloaded(v:val)\")",
1488 [files componentsJoinedByString:@"\",\""]];
1492 if (openFiles != nil)
1498 #if MM_HANDLE_XCODE_MOD_EVENT
1499 - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
1500 replyEvent:(NSAppleEventDescriptor *)reply
1503 // Xcode sends this event to query MacVim which open files have been
1505 ASLogDebug(@"reply:%@", reply);
1506 ASLogDebug(@"event:%@", event);
1508 NSEnumerator *e = [vimControllers objectEnumerator];
1510 while ((vc = [e nextObject])) {
1511 DescType type = [reply descriptorType];
1512 unsigned len = [[type data] length];
1513 NSMutableData *data = [NSMutableData data];
1515 [data appendBytes:&type length:sizeof(DescType)];
1516 [data appendBytes:&len length:sizeof(unsigned)];
1517 [data appendBytes:[reply data] length:len];
1519 [vc sendMessage:XcodeModMsgID data:data];
1525 - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
1526 replyEvent:(NSAppleEventDescriptor *)reply
1528 NSString *urlString = [[event paramDescriptorForKeyword:keyDirectObject]
1530 NSURL *url = [NSURL URLWithString:urlString];
1532 // We try to be compatible with TextMate's URL scheme here, as documented
1533 // at http://blog.macromates.com/2007/the-textmate-url-scheme/ . Currently,
1536 // The format is: mvim://open?<arguments> where arguments can be:
1538 // * url — the actual file to open (i.e. a file://… URL), if you leave
1539 // out this argument, the frontmost document is implied.
1540 // * line — line number to go to (one based).
1541 // * column — column number to go to (one based).
1543 // Example: mvim://open?url=file:///etc/profile&line=20
1545 if ([[url host] isEqualToString:@"open"]) {
1546 NSMutableDictionary *dict = [NSMutableDictionary dictionary];
1548 // Parse query ("url=file://...&line=14") into a dictionary
1549 NSArray *queries = [[url query] componentsSeparatedByString:@"&"];
1550 NSEnumerator *enumerator = [queries objectEnumerator];
1552 while( param = [enumerator nextObject] ) {
1553 NSArray *arr = [param componentsSeparatedByString:@"="];
1554 if ([arr count] == 2) {
1555 [dict setValue:[[arr lastObject]
1556 stringByReplacingPercentEscapesUsingEncoding:
1557 NSUTF8StringEncoding]
1558 forKey:[[arr objectAtIndex:0]
1559 stringByReplacingPercentEscapesUsingEncoding:
1560 NSUTF8StringEncoding]];
1564 // Actually open the file.
1565 NSString *file = [dict objectForKey:@"url"];
1567 NSURL *fileUrl= [NSURL URLWithString:file];
1568 // TextMate only opens files that already exist.
1569 if ([fileUrl isFileURL]
1570 && [[NSFileManager defaultManager] fileExistsAtPath:
1572 // Strip 'file://' path, else application:openFiles: might think
1573 // the file is not yet open.
1574 NSArray *filenames = [NSArray arrayWithObject:[fileUrl path]];
1576 // Look for the line and column options.
1577 NSDictionary *args = nil;
1578 NSString *line = [dict objectForKey:@"line"];
1580 NSString *column = [dict objectForKey:@"column"];
1582 args = [NSDictionary dictionaryWithObjectsAndKeys:
1583 line, @"cursorLine",
1584 column, @"cursorColumn",
1587 args = [NSDictionary dictionaryWithObject:line
1588 forKey:@"cursorLine"];
1591 [self openFiles:filenames withArguments:args];
1595 NSAlert *alert = [[NSAlert alloc] init];
1596 [alert addButtonWithTitle:NSLocalizedString(@"OK",
1599 [alert setMessageText:NSLocalizedString(@"Unknown URL Scheme",
1600 @"Unknown URL Scheme dialog, title")];
1601 [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(
1602 @"This version of MacVim does not support \"%@\""
1603 @" in its URL scheme.",
1604 @"Unknown URL Scheme dialog, text"),
1607 [alert setAlertStyle:NSWarningAlertStyle];
1614 - (int)findLaunchingProcessWithoutArguments
1616 NSArray *keys = [pidArguments allKeysForObject:[NSNull null]];
1617 if ([keys count] > 0)
1618 return [[keys objectAtIndex:0] intValue];
1623 - (MMVimController *)findUnusedEditor
1625 NSEnumerator *e = [vimControllers objectEnumerator];
1627 while ((vc = [e nextObject])) {
1628 if ([[vc objectForVimStateKey:@"unusedEditor"] boolValue])
1635 - (NSMutableDictionary *)extractArgumentsFromOdocEvent:
1636 (NSAppleEventDescriptor *)desc
1638 NSMutableDictionary *dict = [NSMutableDictionary dictionary];
1640 // 1. Extract ODB parameters (if any)
1641 NSAppleEventDescriptor *odbdesc = desc;
1642 if (![odbdesc paramDescriptorForKeyword:keyFileSender]) {
1643 // The ODB paramaters may hide inside the 'keyAEPropData' descriptor.
1644 odbdesc = [odbdesc paramDescriptorForKeyword:keyAEPropData];
1645 if (![odbdesc paramDescriptorForKeyword:keyFileSender])
1650 NSAppleEventDescriptor *p =
1651 [odbdesc paramDescriptorForKeyword:keyFileSender];
1653 [dict setObject:[NSNumber numberWithUnsignedInt:[p typeCodeValue]]
1654 forKey:@"remoteID"];
1656 p = [odbdesc paramDescriptorForKeyword:keyFileCustomPath];
1658 [dict setObject:[p stringValue] forKey:@"remotePath"];
1660 p = [odbdesc paramDescriptorForKeyword:keyFileSenderToken];
1662 [dict setObject:[NSNumber numberWithUnsignedLong:[p descriptorType]]
1663 forKey:@"remoteTokenDescType"];
1664 [dict setObject:[p data] forKey:@"remoteTokenData"];
1668 // 2. Extract Xcode parameters (if any)
1669 NSAppleEventDescriptor *xcodedesc =
1670 [desc paramDescriptorForKeyword:keyAEPosition];
1673 MMSelectionRange *sr = (MMSelectionRange*)[[xcodedesc data] bytes];
1675 if (sr->lineNum < 0) {
1676 // Should select a range of lines.
1677 range.location = sr->startRange + 1;
1678 range.length = sr->endRange - sr->startRange + 1;
1680 // Should only move cursor to a line.
1681 range.location = sr->lineNum + 1;
1685 [dict setObject:NSStringFromRange(range) forKey:@"selectionRange"];
1688 // 3. Extract Spotlight search text (if any)
1689 NSAppleEventDescriptor *spotlightdesc =
1690 [desc paramDescriptorForKeyword:keyAESearchText];
1692 [dict setObject:[spotlightdesc stringValue] forKey:@"searchText"];
1697 #ifdef MM_ENABLE_PLUGINS
1698 - (void)removePlugInMenu
1700 if ([plugInMenuItem menu])
1701 [[plugInMenuItem menu] removeItem:plugInMenuItem];
1704 - (void)addPlugInMenuToMenu:(NSMenu *)mainMenu
1706 NSMenu *windowsMenu = [mainMenu findWindowsMenu];
1708 if ([[plugInMenuItem submenu] numberOfItems] > 0) {
1709 int idx = windowsMenu ? [mainMenu indexOfItemWithSubmenu:windowsMenu]
1712 [mainMenu insertItem:plugInMenuItem atIndex:idx];
1714 [mainMenu addItem:plugInMenuItem];
1720 - (void)scheduleVimControllerPreloadAfterDelay:(NSTimeInterval)delay
1722 [self performSelector:@selector(preloadVimController:)
1727 - (void)cancelVimControllerPreloadRequests
1729 [NSObject cancelPreviousPerformRequestsWithTarget:self
1730 selector:@selector(preloadVimController:)
1734 - (void)preloadVimController:(id)sender
1736 // We only allow preloading of one Vim process at a time (to avoid hogging
1737 // CPU), so schedule another preload in a little while if necessary.
1738 if (-1 != preloadPid) {
1739 [self scheduleVimControllerPreloadAfterDelay:2];
1743 if ([cachedVimControllers count] >= [self maxPreloadCacheSize])
1746 preloadPid = [self launchVimProcessWithArguments:
1747 [NSArray arrayWithObject:@"--mmwaitforack"]];
1750 - (int)maxPreloadCacheSize
1752 // The maximum number of Vim processes to keep in the cache can be
1753 // controlled via the user default "MMPreloadCacheSize".
1754 int maxCacheSize = [[NSUserDefaults standardUserDefaults]
1755 integerForKey:MMPreloadCacheSizeKey];
1756 if (maxCacheSize < 0) maxCacheSize = 0;
1757 else if (maxCacheSize > 10) maxCacheSize = 10;
1759 return maxCacheSize;
1762 - (MMVimController *)takeVimControllerFromCache
1764 // NOTE: After calling this message the backend corresponding to the
1765 // returned vim controller must be sent an acknowledgeConnection message,
1766 // else the vim process will be stuck.
1768 // This method may return nil even though the cache might be non-empty; the
1769 // caller should handle this by starting a new Vim process.
1771 int i, count = [cachedVimControllers count];
1772 if (0 == count) return nil;
1774 // Locate the first Vim controller with up-to-date rc-files sourced.
1775 NSDate *rcDate = [self rcFilesModificationDate];
1776 for (i = 0; i < count; ++i) {
1777 MMVimController *vc = [cachedVimControllers objectAtIndex:i];
1778 NSDate *date = [vc creationDate];
1779 if ([date compare:rcDate] != NSOrderedAscending)
1784 // Clear out cache entries whose vimrc/gvimrc files were sourced before
1785 // the latest modification date for those files. This ensures that the
1786 // latest rc-files are always sourced for new windows.
1787 [self clearPreloadCacheWithCount:i];
1790 if ([cachedVimControllers count] == 0) {
1791 [self scheduleVimControllerPreloadAfterDelay:2.0];
1795 MMVimController *vc = [cachedVimControllers objectAtIndex:0];
1796 [vimControllers addObject:vc];
1797 [cachedVimControllers removeObjectAtIndex:0];
1798 [vc setIsPreloading:NO];
1800 // If the Vim process has finished loading then the window will displayed
1801 // now, otherwise it will be displayed when the OpenWindowMsgID message is
1803 [[vc windowController] showWindow];
1805 // Since we've taken one controller from the cache we take the opportunity
1806 // to preload another.
1807 [self scheduleVimControllerPreloadAfterDelay:1];
1812 - (void)clearPreloadCacheWithCount:(int)count
1814 // Remove the 'count' first entries in the preload cache. It is assumed
1815 // that objects are added/removed from the cache in a FIFO manner so that
1816 // this effectively clears the 'count' oldest entries.
1817 // If 'count' is negative, then the entire cache is cleared.
1819 if ([cachedVimControllers count] == 0 || count == 0)
1823 count = [cachedVimControllers count];
1825 // Make sure the preloaded Vim processes get killed or they'll just hang
1826 // around being useless until MacVim is terminated.
1827 NSEnumerator *e = [cachedVimControllers objectEnumerator];
1828 MMVimController *vc;
1830 while ((vc = [e nextObject]) && n-- > 0) {
1831 [[NSNotificationCenter defaultCenter] removeObserver:vc];
1832 [vc sendMessage:TerminateNowMsgID data:nil];
1834 // Since the preloaded processes were killed "prematurely" we have to
1835 // manually tell them to cleanup (it is not enough to simply release
1836 // them since deallocation and cleanup are separated).
1841 while (n-- > 0 && [cachedVimControllers count] > 0)
1842 [cachedVimControllers removeObjectAtIndex:0];
1844 // There is a small delay before the Vim process actually exits so wait a
1845 // little before trying to reap the child process. If the process still
1846 // hasn't exited after this wait it won't be reaped until the next time
1847 // reapChildProcesses: is called (but this should be harmless).
1848 [self performSelector:@selector(reapChildProcesses:)
1853 - (void)rebuildPreloadCache
1855 if ([self maxPreloadCacheSize] > 0) {
1856 [self clearPreloadCacheWithCount:-1];
1857 [self cancelVimControllerPreloadRequests];
1858 [self scheduleVimControllerPreloadAfterDelay:1.0];
1862 - (NSDate *)rcFilesModificationDate
1864 // Check modification dates for ~/.vimrc and ~/.gvimrc and return the
1865 // latest modification date. If ~/.vimrc does not exist, check ~/_vimrc
1866 // and similarly for gvimrc.
1867 // Returns distantPath if no rc files were found.
1869 NSDate *date = [NSDate distantPast];
1870 NSFileManager *fm = [NSFileManager defaultManager];
1872 NSString *path = [@"~/.vimrc" stringByExpandingTildeInPath];
1873 NSDictionary *attr = [fm fileAttributesAtPath:path traverseLink:YES];
1875 path = [@"~/_vimrc" stringByExpandingTildeInPath];
1876 attr = [fm fileAttributesAtPath:path traverseLink:YES];
1878 NSDate *modDate = [attr objectForKey:NSFileModificationDate];
1882 path = [@"~/.gvimrc" stringByExpandingTildeInPath];
1883 attr = [fm fileAttributesAtPath:path traverseLink:YES];
1885 path = [@"~/_gvimrc" stringByExpandingTildeInPath];
1886 attr = [fm fileAttributesAtPath:path traverseLink:YES];
1888 modDate = [attr objectForKey:NSFileModificationDate];
1890 date = [date laterDate:modDate];
1895 - (BOOL)openVimControllerWithArguments:(NSDictionary *)arguments
1897 MMVimController *vc = [self findUnusedEditor];
1899 // Open files in an already open window.
1900 [[[vc windowController] window] makeKeyAndOrderFront:self];
1901 [vc passArguments:arguments];
1902 } else if ((vc = [self takeVimControllerFromCache])) {
1903 // Open files in a new window using a cached vim controller. This
1904 // requires virtually no loading time so the new window will pop up
1906 [vc passArguments:arguments];
1907 [[vc backendProxy] acknowledgeConnection];
1909 // Open files in a launching Vim process or start a new process. This
1910 // may take 1-2 seconds so there will be a visible delay before the
1911 // window appears on screen.
1912 int pid = [self findLaunchingProcessWithoutArguments];
1914 pid = [self launchVimProcessWithArguments:nil];
1919 // TODO: If the Vim process fails to start, or if it changes PID,
1920 // then the memory allocated for these parameters will leak.
1921 // Ensure that this cannot happen or somehow detect it.
1923 if ([arguments count] > 0)
1924 [pidArguments setObject:arguments
1925 forKey:[NSNumber numberWithInt:pid]];
1931 - (void)activateWhenNextWindowOpens
1933 ASLogDebug(@"Activate MacVim when next window opens");
1934 shouldActivateWhenNextWindowOpens = YES;
1937 - (void)startWatchingVimDir
1939 #if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4)
1942 if (NULL == FSEventStreamStart)
1943 return; // FSEvent functions are weakly linked
1945 NSString *path = [@"~/.vim" stringByExpandingTildeInPath];
1946 NSArray *pathsToWatch = [NSArray arrayWithObject:path];
1948 fsEventStream = FSEventStreamCreate(NULL, &fsEventCallback, NULL,
1949 (CFArrayRef)pathsToWatch, kFSEventStreamEventIdSinceNow,
1950 MMEventStreamLatency, kFSEventStreamCreateFlagNone);
1952 FSEventStreamScheduleWithRunLoop(fsEventStream,
1953 [[NSRunLoop currentRunLoop] getCFRunLoop],
1954 kCFRunLoopDefaultMode);
1956 FSEventStreamStart(fsEventStream);
1957 ASLogDebug(@"Started FS event stream");
1961 - (void)stopWatchingVimDir
1963 #if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4)
1964 if (NULL == FSEventStreamStop)
1965 return; // FSEvent functions are weakly linked
1967 if (fsEventStream) {
1968 FSEventStreamStop(fsEventStream);
1969 FSEventStreamInvalidate(fsEventStream);
1970 FSEventStreamRelease(fsEventStream);
1971 fsEventStream = NULL;
1972 ASLogDebug(@"Stopped FS event stream");
1978 - (void)handleFSEvent
1980 [self clearPreloadCacheWithCount:-1];
1982 // Several FS events may arrive in quick succession so make sure to cancel
1983 // any previous preload requests before making a new one.
1984 [self cancelVimControllerPreloadRequests];
1985 [self scheduleVimControllerPreloadAfterDelay:0.5];
1988 - (void)loadDefaultFont
1990 // It is possible to set a user default to avoid loading the default font
1991 // (this cuts down on startup time).
1992 if (![[NSUserDefaults standardUserDefaults] boolForKey:MMLoadDefaultFontKey]
1993 || fontContainerRef) {
1994 ASLogInfo(@"Skip loading of the default font...");
1998 ASLogInfo(@"Loading the default font...");
2000 // Load all fonts in the Resouces folder of the app bundle.
2001 NSString *fontsFolder = [[NSBundle mainBundle] resourcePath];
2003 NSURL *fontsURL = [NSURL fileURLWithPath:fontsFolder];
2006 CFURLGetFSRef((CFURLRef)fontsURL, &fsRef);
2008 #if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4)
2009 // This is the font activation API for OS X 10.5. Only compile
2010 // this code if we're building on OS X 10.5 or later.
2011 if (NULL != ATSFontActivateFromFileReference) { // Weakly linked
2012 ATSFontActivateFromFileReference(&fsRef, kATSFontContextLocal,
2013 kATSFontFormatUnspecified,
2014 NULL, kATSOptionFlagsDefault,
2018 #if (MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4)
2019 // The following font activation API was deprecated in OS X 10.5.
2020 // Don't compile this code unless we're targeting OS X 10.4.
2022 if (fontContainerRef == 0 &&
2023 FSGetCatalogInfo(&fsRef, kFSCatInfoNone, NULL, NULL,
2024 &fsSpec, NULL) == noErr) {
2025 ATSFontActivateFromFileSpecification(&fsSpec,
2026 kATSFontContextLocal, kATSFontFormatUnspecified, NULL,
2027 kATSOptionFlagsDefault, &fontContainerRef);
2033 if (!fontContainerRef) {
2034 ASLogNotice(@"Failed to activate the default font (the app bundle "
2035 "may be incomplete)");
2039 - (int)executeInLoginShell:(NSString *)path arguments:(NSArray *)args
2041 // Start a login shell and execute the command 'path' with arguments 'args'
2042 // in the shell. This ensures that user environment variables are set even
2043 // when MacVim was started from the Finder.
2046 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
2048 // Determine which shell to use to execute the command. The user
2049 // may decide which shell to use by setting a user default or the
2050 // $SHELL environment variable.
2051 NSString *shell = [ud stringForKey:MMLoginShellCommandKey];
2052 if (!shell || [shell length] == 0)
2053 shell = [[[NSProcessInfo processInfo] environment]
2054 objectForKey:@"SHELL"];
2056 shell = @"/bin/bash";
2058 // Bash needs the '-l' flag to launch a login shell. The user may add
2059 // flags by setting a user default.
2060 NSString *shellArgument = [ud stringForKey:MMLoginShellArgumentKey];
2061 if (!shellArgument || [shellArgument length] == 0) {
2062 if ([[shell lastPathComponent] isEqual:@"bash"])
2063 shellArgument = @"-l";
2065 shellArgument = nil;
2068 // Build input string to pipe to the login shell.
2069 NSMutableString *input = [NSMutableString stringWithFormat:
2070 @"exec \"%@\"", path];
2072 // Append all arguments, making sure they are properly quoted, even
2073 // when they contain single quotes.
2074 NSEnumerator *e = [args objectEnumerator];
2077 while ((obj = [e nextObject])) {
2078 NSMutableString *arg = [NSMutableString stringWithString:obj];
2079 [arg replaceOccurrencesOfString:@"'" withString:@"'\"'\"'"
2080 options:NSLiteralSearch
2081 range:NSMakeRange(0, [arg length])];
2082 [input appendFormat:@" '%@'", arg];
2086 // Build the argument vector used to start the login shell.
2087 NSString *shellArg0 = [NSString stringWithFormat:@"-%@",
2088 [shell lastPathComponent]];
2089 char *shellArgv[3] = { (char *)[shellArg0 UTF8String], NULL, NULL };
2091 shellArgv[1] = (char *)[shellArgument UTF8String];
2093 // Get the C string representation of the shell path before the fork since
2094 // we must not call Foundation functions after a fork.
2095 const char *shellPath = [shell fileSystemRepresentation];
2097 // Fork and execute the process.
2099 if (pipe(ds)) return -1;
2104 } else if (pid == 0) {
2107 if (close(ds[1]) == -1) exit(255);
2108 if (dup2(ds[0], 0) == -1) exit(255);
2110 // Without the following call warning messages like this appear on the
2112 // com.apple.launchd[69] : Stray process with PGID equal to this
2113 // dead job: PID 1589 PPID 1 Vim
2116 execv(shellPath, shellArgv);
2118 // Never reached unless execv fails
2122 if (close(ds[0]) == -1) return -1;
2124 // Send input to execute to the child process
2125 [input appendString:@"\n"];
2126 int bytes = [input lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2128 if (write(ds[1], [input UTF8String], bytes) != bytes) return -1;
2129 if (close(ds[1]) == -1) return -1;
2131 ++numChildProcesses;
2132 ASLogDebug(@"new process pid=%d (count=%d)", pid, numChildProcesses);
2138 - (void)reapChildProcesses:(id)sender
2140 // NOTE: numChildProcesses (currently) only counts the number of Vim
2141 // processes that have been started with executeInLoginShell::. If other
2142 // processes are spawned this code may need to be adjusted (or
2143 // numChildProcesses needs to be incremented when such a process is
2145 while (numChildProcesses > 0) {
2147 int pid = waitpid(-1, &status, WNOHANG);
2151 ASLogDebug(@"Wait for pid=%d complete", pid);
2152 --numChildProcesses;
2156 - (void)processInputQueues:(id)sender
2158 // NOTE: Because we use distributed objects it is quite possible for this
2159 // function to be re-entered. This can cause all sorts of unexpected
2160 // problems so we guard against it here so that the rest of the code does
2161 // not need to worry about it.
2163 // The processing flag is > 0 if this function is already on the call
2164 // stack; < 0 if this function was also re-entered.
2165 if (processingFlag != 0) {
2166 ASLogDebug(@"BUSY!");
2167 processingFlag = -1;
2171 // NOTE: Be _very_ careful that no exceptions can be raised between here
2172 // and the point at which 'processingFlag' is reset. Otherwise the above
2173 // test could end up always failing and no input queues would ever be
2177 // NOTE: New input may arrive while we're busy processing; we deal with
2178 // this by putting the current queue aside and creating a new input queue
2179 // for future input.
2180 NSDictionary *queues = inputQueues;
2181 inputQueues = [NSMutableDictionary new];
2183 // Pass each input queue on to the vim controller with matching
2184 // identifier (and note that it could be cached).
2185 NSEnumerator *e = [queues keyEnumerator];
2187 while ((key = [e nextObject])) {
2188 unsigned ukey = [key unsignedIntValue];
2189 int i = 0, count = [vimControllers count];
2190 for (i = 0; i < count; ++i) {
2191 MMVimController *vc = [vimControllers objectAtIndex:i];
2192 if (ukey == [vc identifier]) {
2193 [vc processInputQueue:[queues objectForKey:key]]; // !exceptions
2198 if (i < count) continue;
2200 count = [cachedVimControllers count];
2201 for (i = 0; i < count; ++i) {
2202 MMVimController *vc = [cachedVimControllers objectAtIndex:i];
2203 if (ukey == [vc identifier]) {
2204 [vc processInputQueue:[queues objectForKey:key]]; // !exceptions
2210 ASLogWarn(@"No Vim controller for identifier=%d", ukey);
2216 // If new input arrived while we were processing it would have been
2217 // blocked so we have to schedule it to be processed again.
2218 if (processingFlag < 0)
2219 [self performSelector:@selector(processInputQueues:)
2222 inModes:[NSArray arrayWithObjects:NSDefaultRunLoopMode,
2223 NSEventTrackingRunLoopMode, nil]];
2228 - (void)addVimController:(MMVimController *)vc
2230 ASLogDebug(@"Add Vim controller pid=%d id=%d", [vc pid], [vc identifier]);
2233 NSNumber *pidKey = [NSNumber numberWithInt:pid];
2235 if (preloadPid == pid) {
2236 // This controller was preloaded, so add it to the cache and
2237 // schedule another vim process to be preloaded.
2239 [vc setIsPreloading:YES];
2240 [cachedVimControllers addObject:vc];
2241 [self scheduleVimControllerPreloadAfterDelay:1];
2243 [vimControllers addObject:vc];
2245 id args = [pidArguments objectForKey:pidKey];
2246 if (args && [NSNull null] != args)
2247 [vc passArguments:args];
2249 // HACK! MacVim does not get activated if it is launched from the
2250 // terminal, so we forcibly activate here unless it is an untitled
2251 // window opening. Untitled windows are treated differently, else
2252 // MacVim would steal the focus if another app was activated while the
2253 // untitled window was loading.
2254 if (!args || args != [NSNull null])
2255 [self activateWhenNextWindowOpens];
2258 [pidArguments removeObjectForKey:pidKey];
2262 @end // MMAppController (Private)