Avoid "Stray process..." warning messages
[MacVim.git] / src / MacVim / MMAppController.m
blob91307d9b381512d17a9a5eb11d79d79bf8f4c84f
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
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.
9  */
11  * MMAppController
12  *
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.
17  *
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
22  * MMVimController.
23  *
24  * A Vim process started from the command line connects directly by sending the
25  * connectBackend:pid: message (launchVimProcessWithArguments: is never called
26  * in this case).
27  *
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".
38  */
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"
48 #endif
50 #import <unistd.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 // When terminating, notify Vim processes then sleep for these many
65 // microseconds.
66 static useconds_t MMTerminationSleepPeriod = 10000;
68 #if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4)
69 // Latency (in s) between FS event occuring and being reported to MacVim.
70 // Should be small so that MacVim is notified of changes to the ~/.vim
71 // directory more or less immediately.
72 static CFTimeInterval MMEventStreamLatency = 0.1;
73 #endif
76 #pragma options align=mac68k
77 typedef struct
79     short unused1;      // 0 (not used)
80     short lineNum;      // line to select (< 0 to specify range)
81     long  startRange;   // start of selection range (if line < 0)
82     long  endRange;     // end of selection range (if line < 0)
83     long  unused2;      // 0 (not used)
84     long  theDate;      // modification date/time
85 } MMSelectionRange;
86 #pragma options align=reset
89 static int executeInLoginShell(NSString *path, NSArray *args);
92 @interface MMAppController (MMServices)
93 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
94                 error:(NSString **)error;
95 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
96            error:(NSString **)error;
97 - (void)newFileHere:(NSPasteboard *)pboard userData:(NSString *)userData
98               error:(NSString **)error;
99 @end
102 @interface MMAppController (Private)
103 - (MMVimController *)topmostVimController;
104 - (int)launchVimProcessWithArguments:(NSArray *)args;
105 - (NSArray *)filterFilesAndNotify:(NSArray *)files;
106 - (NSArray *)filterOpenFiles:(NSArray *)filenames
107                openFilesDict:(NSDictionary **)openFiles;
108 #if MM_HANDLE_XCODE_MOD_EVENT
109 - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
110                  replyEvent:(NSAppleEventDescriptor *)reply;
111 #endif
112 - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
113                replyEvent:(NSAppleEventDescriptor *)reply;
114 - (int)findLaunchingProcessWithoutArguments;
115 - (MMVimController *)findUnusedEditor;
116 - (NSMutableDictionary *)extractArgumentsFromOdocEvent:
117     (NSAppleEventDescriptor *)desc;
118 - (void)scheduleVimControllerPreloadAfterDelay:(NSTimeInterval)delay;
119 - (void)cancelVimControllerPreloadRequests;
120 - (void)preloadVimController:(id)sender;
121 - (int)maxPreloadCacheSize;
122 - (MMVimController *)takeVimControllerFromCache;
123 - (void)clearPreloadCacheWithCount:(int)count;
124 - (void)rebuildPreloadCache;
125 - (NSDate *)rcFilesModificationDate;
126 - (BOOL)openVimControllerWithArguments:(NSDictionary *)arguments;
127 - (void)activateWhenNextWindowOpens;
128 - (void)startWatchingVimDir;
129 - (void)stopWatchingVimDir;
130 - (void)handleFSEvent;
132 #ifdef MM_ENABLE_PLUGINS
133 - (void)removePlugInMenu;
134 - (void)addPlugInMenuToMenu:(NSMenu *)mainMenu;
135 #endif
136 @end
140 #if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4)
141     static void
142 fsEventCallback(ConstFSEventStreamRef streamRef,
143                 void *clientCallBackInfo,
144                 size_t numEvents,
145                 void *eventPaths,
146                 const FSEventStreamEventFlags eventFlags[],
147                 const FSEventStreamEventId eventIds[])
149     [[MMAppController sharedInstance] handleFSEvent];
151 #endif
153 @implementation MMAppController
155 + (void)initialize
157     // Avoid zombies (we fork Vim processes which we don't want to wait for).
158     signal(SIGCHLD, SIG_IGN);
160     NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
161         [NSNumber numberWithBool:NO],   MMNoWindowKey,
162         [NSNumber numberWithInt:64],    MMTabMinWidthKey,
163         [NSNumber numberWithInt:6*64],  MMTabMaxWidthKey,
164         [NSNumber numberWithInt:132],   MMTabOptimumWidthKey,
165         [NSNumber numberWithInt:2],     MMTextInsetLeftKey,
166         [NSNumber numberWithInt:1],     MMTextInsetRightKey,
167         [NSNumber numberWithInt:1],     MMTextInsetTopKey,
168         [NSNumber numberWithInt:1],     MMTextInsetBottomKey,
169         @"MMTypesetter",                MMTypesetterKey,
170         [NSNumber numberWithFloat:1],   MMCellWidthMultiplierKey,
171         [NSNumber numberWithFloat:-1],  MMBaselineOffsetKey,
172         [NSNumber numberWithBool:YES],  MMTranslateCtrlClickKey,
173         [NSNumber numberWithInt:0],     MMOpenInCurrentWindowKey,
174         [NSNumber numberWithBool:NO],   MMNoFontSubstitutionKey,
175         [NSNumber numberWithBool:YES],  MMLoginShellKey,
176         [NSNumber numberWithBool:NO],   MMAtsuiRendererKey,
177         [NSNumber numberWithInt:MMUntitledWindowAlways],
178                                         MMUntitledWindowKey,
179         [NSNumber numberWithBool:NO],   MMTexturedWindowKey,
180         [NSNumber numberWithBool:NO],   MMZoomBothKey,
181         @"",                            MMLoginShellCommandKey,
182         @"",                            MMLoginShellArgumentKey,
183         [NSNumber numberWithBool:YES],  MMDialogsTrackPwdKey,
184 #ifdef MM_ENABLE_PLUGINS
185         [NSNumber numberWithBool:YES],  MMShowLeftPlugInContainerKey,
186 #endif
187         [NSNumber numberWithInt:3],     MMOpenLayoutKey,
188         [NSNumber numberWithBool:NO],   MMVerticalSplitKey,
189         [NSNumber numberWithInt:0],     MMPreloadCacheSizeKey,
190         [NSNumber numberWithInt:0],     MMLastWindowClosedBehaviorKey,
191         nil];
193     [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
195     NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
196     [NSApp registerServicesMenuSendTypes:types returnTypes:types];
198     // NOTE: Set the current directory to user's home directory, otherwise it
199     // will default to the root directory.  (This matters since new Vim
200     // processes inherit MacVim's environment variables.)
201     [[NSFileManager defaultManager] changeCurrentDirectoryPath:
202             NSHomeDirectory()];
205 - (id)init
207     if (!(self = [super init])) return nil;
209     fontContainerRef = loadFonts();
211     vimControllers = [NSMutableArray new];
212     cachedVimControllers = [NSMutableArray new];
213     preloadPid = -1;
214     pidArguments = [NSMutableDictionary new];
216 #ifdef MM_ENABLE_PLUGINS
217     NSString *plugInTitle = NSLocalizedString(@"Plug-In",
218                                               @"Plug-In menu title");
219     plugInMenuItem = [[NSMenuItem alloc] initWithTitle:plugInTitle
220                                                 action:NULL
221                                          keyEquivalent:@""];
222     NSMenu *submenu = [[NSMenu alloc] initWithTitle:plugInTitle];
223     [plugInMenuItem setSubmenu:submenu];
224     [submenu release];
225 #endif
227     // NOTE: Do not use the default connection since the Logitech Control
228     // Center (LCC) input manager steals and this would cause MacVim to
229     // never open any windows.  (This is a bug in LCC but since they are
230     // unlikely to fix it, we graciously give them the default connection.)
231     connection = [[NSConnection alloc] initWithReceivePort:[NSPort port]
232                                                   sendPort:nil];
233     [connection setRootObject:self];
234     [connection setRequestTimeout:MMRequestTimeout];
235     [connection setReplyTimeout:MMReplyTimeout];
237     // NOTE!  If the name of the connection changes here it must also be
238     // updated in MMBackend.m.
239     NSString *name = [NSString stringWithFormat:@"%@-connection",
240              [[NSBundle mainBundle] bundlePath]];
241     //NSLog(@"Registering connection with name '%@'", name);
242     if (![connection registerName:name]) {
243         NSLog(@"FATAL ERROR: Failed to register connection with name '%@'",
244                 name);
245         [connection release];  connection = nil;
246     }
248     return self;
251 - (void)dealloc
253     //NSLog(@"MMAppController dealloc");
255     [connection release];  connection = nil;
256     [pidArguments release];  pidArguments = nil;
257     [vimControllers release];  vimControllers = nil;
258     [cachedVimControllers release];  cachedVimControllers = nil;
259     [openSelectionString release];  openSelectionString = nil;
260     [recentFilesMenuItem release];  recentFilesMenuItem = nil;
261     [defaultMainMenu release];  defaultMainMenu = nil;
262 #ifdef MM_ENABLE_PLUGINS
263     [plugInMenuItem release];  plugInMenuItem = nil;
264 #endif
265     [appMenuItemTemplate release];  appMenuItemTemplate = nil;
267     [super dealloc];
270 - (void)applicationWillFinishLaunching:(NSNotification *)notification
272     // Remember the default menu so that it can be restored if the user closes
273     // all editor windows.
274     defaultMainMenu = [[NSApp mainMenu] retain];
276     // Store a copy of the default app menu so we can use this as a template
277     // for all other menus.  We make a copy here because the "Services" menu
278     // will not yet have been populated at this time.  If we don't we get
279     // problems trying to set key equivalents later on because they might clash
280     // with items on the "Services" menu.
281     appMenuItemTemplate = [defaultMainMenu itemAtIndex:0];
282     appMenuItemTemplate = [appMenuItemTemplate copy];
284     // Set up the "Open Recent" menu. See
285     //   http://lapcatsoftware.com/blog/2007/07/10/
286     //     working-without-a-nib-part-5-open-recent-menu/
287     // and
288     //   http://www.cocoabuilder.com/archive/message/cocoa/2007/8/15/187793
289     // for more information.
290     //
291     // The menu itself is created in MainMenu.nib but we still seem to have to
292     // hack around a bit to get it to work.  (This has to be done in
293     // applicationWillFinishLaunching at the latest, otherwise it doesn't
294     // work.)
295     NSMenu *fileMenu = [defaultMainMenu findFileMenu];
296     if (fileMenu) {
297         int idx = [fileMenu indexOfItemWithAction:@selector(fileOpen:)];
298         if (idx >= 0 && idx+1 < [fileMenu numberOfItems])
300         recentFilesMenuItem = [fileMenu itemWithTitle:@"Open Recent"];
301         [[recentFilesMenuItem submenu] performSelector:@selector(_setMenuName:)
302                                         withObject:@"NSRecentDocumentsMenu"];
304         // Note: The "Recent Files" menu must be moved around since there is no
305         // -[NSApp setRecentFilesMenu:] method.  We keep a reference to it to
306         // facilitate this move (see setMainMenu: below).
307         [recentFilesMenuItem retain];
308     }
310 #if MM_HANDLE_XCODE_MOD_EVENT
311     [[NSAppleEventManager sharedAppleEventManager]
312             setEventHandler:self
313                 andSelector:@selector(handleXcodeModEvent:replyEvent:)
314               forEventClass:'KAHL'
315                  andEventID:'MOD '];
316 #endif
318     // Register 'mvim://' URL handler
319     [[NSAppleEventManager sharedAppleEventManager]
320             setEventHandler:self
321                 andSelector:@selector(handleGetURLEvent:replyEvent:)
322               forEventClass:kInternetEventClass
323                  andEventID:kAEGetURL];
326 - (void)applicationDidFinishLaunching:(NSNotification *)notification
328     [NSApp setServicesProvider:self];
329 #ifdef MM_ENABLE_PLUGINS
330     [[MMPlugInManager sharedManager] loadAllPlugIns];
331 #endif
333     if ([self maxPreloadCacheSize] > 0) {
334         [self scheduleVimControllerPreloadAfterDelay:2];
335         [self startWatchingVimDir];
336     }
339 - (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
341     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
342     NSAppleEventManager *aem = [NSAppleEventManager sharedAppleEventManager];
343     NSAppleEventDescriptor *desc = [aem currentAppleEvent];
345     // The user default MMUntitledWindow can be set to control whether an
346     // untitled window should open on 'Open' and 'Reopen' events.
347     int untitledWindowFlag = [ud integerForKey:MMUntitledWindowKey];
348     if ([desc eventID] == kAEOpenApplication
349             && (untitledWindowFlag & MMUntitledWindowOnOpen) == 0)
350         return NO;
351     else if ([desc eventID] == kAEReopenApplication
352             && (untitledWindowFlag & MMUntitledWindowOnReopen) == 0)
353         return NO;
355     // When a process is started from the command line, the 'Open' event will
356     // contain a parameter to surpress the opening of an untitled window.
357     desc = [desc paramDescriptorForKeyword:keyAEPropData];
358     desc = [desc paramDescriptorForKeyword:keyMMUntitledWindow];
359     if (desc && ![desc booleanValue])
360         return NO;
362     // Never open an untitled window if there is at least one open window or if
363     // there are processes that are currently launching.
364     if ([vimControllers count] > 0 || [pidArguments count] > 0)
365         return NO;
367     // NOTE!  This way it possible to start the app with the command-line
368     // argument '-nowindow yes' and no window will be opened by default.
369     return ![ud boolForKey:MMNoWindowKey];
372 - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
374     [self newWindow:self];
375     return YES;
378 - (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
380     // Extract ODB/Xcode/Spotlight parameters from the current Apple event,
381     // sort the filenames, and then let openFiles:withArguments: do the heavy
382     // lifting.
384     if (!(filenames && [filenames count] > 0))
385         return;
387     // Sort filenames since the Finder doesn't take care in preserving the
388     // order in which files are selected anyway (and "sorted" is more
389     // predictable than "random").
390     if ([filenames count] > 1)
391         filenames = [filenames sortedArrayUsingSelector:
392                 @selector(localizedCompare:)];
394     // Extract ODB/Xcode/Spotlight parameters from the current Apple event
395     NSMutableDictionary *arguments = [self extractArgumentsFromOdocEvent:
396             [[NSAppleEventManager sharedAppleEventManager] currentAppleEvent]];
398     if ([self openFiles:filenames withArguments:arguments]) {
399         [NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
400     } else {
401         // TODO: Notify user of failure?
402         [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
403     }
406 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
408     return (MMTerminateWhenLastWindowClosed ==
409             [[NSUserDefaults standardUserDefaults]
410                 integerForKey:MMLastWindowClosedBehaviorKey]);
413 - (NSApplicationTerminateReply)applicationShouldTerminate:
414     (NSApplication *)sender
416     // TODO: Follow Apple's guidelines for 'Graceful Application Termination'
417     // (in particular, allow user to review changes and save).
418     int reply = NSTerminateNow;
419     BOOL modifiedBuffers = NO;
421     // Go through windows, checking for modified buffers.  (Each Vim process
422     // tells MacVim when any buffer has been modified and MacVim sets the
423     // 'documentEdited' flag of the window correspondingly.)
424     NSEnumerator *e = [[NSApp windows] objectEnumerator];
425     id window;
426     while ((window = [e nextObject])) {
427         if ([window isDocumentEdited]) {
428             modifiedBuffers = YES;
429             break;
430         }
431     }
433     if (modifiedBuffers) {
434         NSAlert *alert = [[NSAlert alloc] init];
435         [alert setAlertStyle:NSWarningAlertStyle];
436         [alert addButtonWithTitle:NSLocalizedString(@"Quit",
437                 @"Dialog button")];
438         [alert addButtonWithTitle:NSLocalizedString(@"Cancel",
439                 @"Dialog button")];
440         [alert setMessageText:NSLocalizedString(@"Quit without saving?",
441                 @"Quit dialog with changed buffers, title")];
442         [alert setInformativeText:NSLocalizedString(
443                 @"There are modified buffers, "
444                 "if you quit now all changes will be lost.  Quit anyway?",
445                 @"Quit dialog with changed buffers, text")];
447         if ([alert runModal] != NSAlertFirstButtonReturn)
448             reply = NSTerminateCancel;
450         [alert release];
451     } else {
452         // No unmodified buffers, but give a warning if there are multiple
453         // windows and/or tabs open.
454         int numWindows = [vimControllers count];
455         int numTabs = 0;
457         // Count the number of open tabs
458         e = [vimControllers objectEnumerator];
459         id vc;
460         while ((vc = [e nextObject])) {
461             NSString *eval = [vc evaluateVimExpression:@"tabpagenr('$')"];
462             if (eval) {
463                 int count = [eval intValue];
464                 if (count > 0 && count < INT_MAX)
465                     numTabs += count;
466             }
467         }
469         if (numWindows > 1 || numTabs > 1) {
470             NSAlert *alert = [[NSAlert alloc] init];
471             [alert setAlertStyle:NSWarningAlertStyle];
472             [alert addButtonWithTitle:NSLocalizedString(@"Quit",
473                     @"Dialog button")];
474             [alert addButtonWithTitle:NSLocalizedString(@"Cancel",
475                     @"Dialog button")];
476             [alert setMessageText:NSLocalizedString(
477                     @"Are you sure you want to quit MacVim?",
478                     @"Quit dialog with no changed buffers, title")];
480             NSString *info = nil;
481             if (numWindows > 1) {
482                 if (numTabs > numWindows)
483                     info = [NSString stringWithFormat:NSLocalizedString(
484                             @"There are %d windows open in MacVim, with a "
485                             "total of %d tabs. Do you want to quit anyway?",
486                             @"Quit dialog with no changed buffers, text"),
487                          numWindows, numTabs];
488                 else
489                     info = [NSString stringWithFormat:NSLocalizedString(
490                             @"There are %d windows open in MacVim. "
491                             "Do you want to quit anyway?",
492                             @"Quit dialog with no changed buffers, text"),
493                         numWindows];
495             } else {
496                 info = [NSString stringWithFormat:NSLocalizedString(
497                         @"There are %d tabs open in MacVim. "
498                         "Do you want to quit anyway?",
499                         @"Quit dialog with no changed buffers, text"), 
500                      numTabs];
501             }
503             [alert setInformativeText:info];
505             if ([alert runModal] != NSAlertFirstButtonReturn)
506                 reply = NSTerminateCancel;
508             [alert release];
509         }
510     }
513     // Tell all Vim processes to terminate now (otherwise they'll leave swap
514     // files behind).
515     if (NSTerminateNow == reply) {
516         e = [vimControllers objectEnumerator];
517         id vc;
518         while ((vc = [e nextObject]))
519             [vc sendMessage:TerminateNowMsgID data:nil];
521         e = [cachedVimControllers objectEnumerator];
522         while ((vc = [e nextObject]))
523             [vc sendMessage:TerminateNowMsgID data:nil];
525         // Give Vim processes a chance to terminate before MacVim.  If they
526         // haven't terminated by the time applicationWillTerminate: is sent,
527         // they may be forced to quit (see below).
528         usleep(MMTerminationSleepPeriod);
529     }
531     return reply;
534 - (void)applicationWillTerminate:(NSNotification *)notification
536     [self stopWatchingVimDir];
538 #ifdef MM_ENABLE_PLUGINS
539     [[MMPlugInManager sharedManager] unloadAllPlugIns];
540 #endif
542 #if MM_HANDLE_XCODE_MOD_EVENT
543     [[NSAppleEventManager sharedAppleEventManager]
544             removeEventHandlerForEventClass:'KAHL'
545                                  andEventID:'MOD '];
546 #endif
548     // This will invalidate all connections (since they were spawned from this
549     // connection).
550     [connection invalidate];
552     // Send a SIGINT to all running Vim processes, so that they are sure to
553     // receive the connectionDidDie: notification (a process has to be checking
554     // the run-loop for this to happen).
555     unsigned i, count = [vimControllers count];
556     for (i = 0; i < count; ++i) {
557         MMVimController *controller = [vimControllers objectAtIndex:i];
558         int pid = [controller pid];
559         if (-1 != pid)
560             kill(pid, SIGINT);
561     }
563     if (fontContainerRef) {
564         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
565         fontContainerRef = 0;
566     }
568     [NSApp setDelegate:nil];
571 + (MMAppController *)sharedInstance
573     // Note: The app controller is a singleton which is instantiated in
574     // MainMenu.nib where it is also connected as the delegate of NSApp.
575     id delegate = [NSApp delegate];
576     return [delegate isKindOfClass:self] ? (MMAppController*)delegate : nil;
579 - (NSMenu *)defaultMainMenu
581     return defaultMainMenu;
584 - (NSMenuItem *)appMenuItemTemplate
586     return appMenuItemTemplate;
589 - (void)removeVimController:(id)controller
591     int idx = [vimControllers indexOfObject:controller];
592     if (NSNotFound == idx)
593         return;
595     [controller cleanup];
597     [vimControllers removeObjectAtIndex:idx];
599     if (![vimControllers count]) {
600         // The last editor window just closed so restore the main menu back to
601         // its default state (which is defined in MainMenu.nib).
602         [self setMainMenu:defaultMainMenu];
604         BOOL hide = (MMHideWhenLastWindowClosed ==
605                     [[NSUserDefaults standardUserDefaults]
606                         integerForKey:MMLastWindowClosedBehaviorKey]);
607         if (hide)
608             [NSApp hide:self];
609     }
612 - (void)windowControllerWillOpen:(MMWindowController *)windowController
614     NSPoint topLeft = NSZeroPoint;
615     NSWindow *topWin = [[[self topmostVimController] windowController] window];
616     NSWindow *win = [windowController window];
618     if (!win) return;
620     // If there is a window belonging to a Vim process, cascade from it,
621     // otherwise use the autosaved window position (if any).
622     if (topWin) {
623         NSRect frame = [topWin frame];
624         topLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
625     } else {
626         NSString *topLeftString = [[NSUserDefaults standardUserDefaults]
627             stringForKey:MMTopLeftPointKey];
628         if (topLeftString)
629             topLeft = NSPointFromString(topLeftString);
630     }
632     if (!NSEqualPoints(topLeft, NSZeroPoint)) {
633         NSPoint oldTopLeft = topLeft;
634         if (topWin)
635             topLeft = [win cascadeTopLeftFromPoint:topLeft];
637         [win setFrameTopLeftPoint:topLeft];
639         NSPoint screenOrigin = [[win screen] frame].origin;
640         if ([win frame].origin.y < screenOrigin.y) {
641             // Try to avoid shifting the new window downwards if it means that
642             // the bottom of the window will be off the screen.  E.g. if the
643             // user has set windows to open maximized in the vertical direction
644             // then the new window will cascade horizontally only.
645             topLeft.y = oldTopLeft.y;
646             [win setFrameTopLeftPoint:topLeft];
647         }
649         if ([win frame].origin.y < screenOrigin.y) {
650             // Move the window to the top of the screen if the bottom of the
651             // window is still obscured.
652             topLeft.y = NSMaxY([[win screen] frame]);
653             [win setFrameTopLeftPoint:topLeft];
654         }
655     }
657     if (1 == [vimControllers count]) {
658         // The first window autosaves its position.  (The autosaving
659         // features of Cocoa are not used because we need more control over
660         // what is autosaved and when it is restored.)
661         [windowController setWindowAutosaveKey:MMTopLeftPointKey];
662     }
664     if (openSelectionString) {
665         // TODO: Pass this as a parameter instead!  Get rid of
666         // 'openSelectionString' etc.
667         //
668         // There is some text to paste into this window as a result of the
669         // services menu "Open selection ..." being used.
670         [[windowController vimController] dropString:openSelectionString];
671         [openSelectionString release];
672         openSelectionString = nil;
673     }
675     if (shouldActivateWhenNextWindowOpens) {
676         [NSApp activateIgnoringOtherApps:YES];
677         shouldActivateWhenNextWindowOpens = NO;
678     }
681 - (void)setMainMenu:(NSMenu *)mainMenu
683     if ([NSApp mainMenu] == mainMenu) return;
685     // If the new menu has a "Recent Files" dummy item, then swap the real item
686     // for the dummy.  We are forced to do this since Cocoa initializes the
687     // "Recent Files" menu and there is no way to simply point Cocoa to a new
688     // item each time the menus are swapped.
689     NSMenu *fileMenu = [mainMenu findFileMenu];
690     if (recentFilesMenuItem && fileMenu) {
691         int dummyIdx =
692                 [fileMenu indexOfItemWithAction:@selector(recentFilesDummy:)];
693         if (dummyIdx >= 0) {
694             NSMenuItem *dummyItem = [[fileMenu itemAtIndex:dummyIdx] retain];
695             [fileMenu removeItemAtIndex:dummyIdx];
697             NSMenu *recentFilesParentMenu = [recentFilesMenuItem menu];
698             int idx = [recentFilesParentMenu indexOfItem:recentFilesMenuItem];
699             if (idx >= 0) {
700                 [[recentFilesMenuItem retain] autorelease];
701                 [recentFilesParentMenu removeItemAtIndex:idx];
702                 [recentFilesParentMenu insertItem:dummyItem atIndex:idx];
703             }
705             [fileMenu insertItem:recentFilesMenuItem atIndex:dummyIdx];
706             [dummyItem release];
707         }
708     }
710     // Now set the new menu.  Notice that we keep one menu for each editor
711     // window since each editor can have its own set of menus.  When swapping
712     // menus we have to tell Cocoa where the new "MacVim", "Windows", and
713     // "Services" menu are.
714     [NSApp setMainMenu:mainMenu];
716     // Setting the "MacVim" (or "Application") menu ensures that it is typeset
717     // in boldface.  (The setAppleMenu: method used to be public but is now
718     // private so this will have to be considered a bit of a hack!)
719     NSMenu *appMenu = [mainMenu findApplicationMenu];
720     [NSApp performSelector:@selector(setAppleMenu:) withObject:appMenu];
722     NSMenu *servicesMenu = [mainMenu findServicesMenu];
723     [NSApp setServicesMenu:servicesMenu];
725     NSMenu *windowsMenu = [mainMenu findWindowsMenu];
726     if (windowsMenu) {
727         // Cocoa isn't clever enough to get rid of items it has added to the
728         // "Windows" menu so we have to do it ourselves otherwise there will be
729         // multiple menu items for each window in the "Windows" menu.
730         //   This code assumes that the only items Cocoa add are ones which
731         // send off the action makeKeyAndOrderFront:.  (Cocoa will not add
732         // another separator item if the last item on the "Windows" menu
733         // already is a separator, so we needen't worry about separators.)
734         int i, count = [windowsMenu numberOfItems];
735         for (i = count-1; i >= 0; --i) {
736             NSMenuItem *item = [windowsMenu itemAtIndex:i];
737             if ([item action] == @selector(makeKeyAndOrderFront:))
738                 [windowsMenu removeItem:item];
739         }
740     }
741     [NSApp setWindowsMenu:windowsMenu];
743 #ifdef MM_ENABLE_PLUGINS
744     // Move plugin menu from old to new main menu.
745     [self removePlugInMenu];
746     [self addPlugInMenuToMenu:mainMenu];
747 #endif
750 - (NSArray *)filterOpenFiles:(NSArray *)filenames
752     return [self filterOpenFiles:filenames openFilesDict:nil];
755 - (BOOL)openFiles:(NSArray *)filenames withArguments:(NSDictionary *)args
757     // Opening files works like this:
758     //  a) filter out any already open files
759     //  b) open any remaining files
760     //
761     // A file is opened in an untitled window if there is one (it may be
762     // currently launching, or it may already be visible), otherwise a new
763     // window is opened.
764     //
765     // Each launching Vim process has a dictionary of arguments that are passed
766     // to the process when in checks in (via connectBackend:pid:).  The
767     // arguments for each launching process can be looked up by its PID (in the
768     // pidArguments dictionary).
770     NSMutableDictionary *arguments = (args ? [[args mutableCopy] autorelease]
771                                            : [NSMutableDictionary dictionary]);
773     //
774     // a) Filter out any already open files
775     //
776     NSString *firstFile = [filenames objectAtIndex:0];
777     MMVimController *firstController = nil;
778     NSDictionary *openFilesDict = nil;
779     filenames = [self filterOpenFiles:filenames openFilesDict:&openFilesDict];
781     // Pass arguments to vim controllers that had files open.
782     id key;
783     NSEnumerator *e = [openFilesDict keyEnumerator];
785     // (Indicate that we do not wish to open any files at the moment.)
786     [arguments setObject:[NSNumber numberWithBool:YES] forKey:@"dontOpen"];
788     while ((key = [e nextObject])) {
789         NSArray *files = [openFilesDict objectForKey:key];
790         [arguments setObject:files forKey:@"filenames"];
792         MMVimController *vc = [key pointerValue];
793         [vc passArguments:arguments];
795         // If this controller holds the first file, then remember it for later.
796         if ([files containsObject:firstFile])
797             firstController = vc;
798     }
800     if ([filenames count] == 0) {
801         // Raise the window containing the first file that was already open,
802         // and make sure that the tab containing that file is selected.  Only
803         // do this when there are no more files to open, otherwise sometimes
804         // the window with 'firstFile' will be raised, other times it might be
805         // the window that will open with the files in the 'filenames' array.
806         firstFile = [firstFile stringByEscapingSpecialFilenameCharacters];
807         NSString *input = [NSString stringWithFormat:@"<C-\\><C-N>"
808                 ":let oldswb=&swb|let &swb=\"useopen,usetab\"|"
809                 "tab sb %@|let &swb=oldswb|unl oldswb|"
810                 "cal foreground()|redr|f<CR>", firstFile];
812         [firstController addVimInput:input];
814         return YES;
815     }
817     // Add filenames to "Recent Files" menu, unless they are being edited
818     // remotely (using ODB).
819     if ([arguments objectForKey:@"remoteID"] == nil) {
820         [[NSDocumentController sharedDocumentController]
821                 noteNewRecentFilePaths:filenames];
822     }
824     //
825     // b) Open any remaining files
826     //
827     MMVimController *vc;
828     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
829     BOOL openInCurrentWindow = [ud boolForKey:MMOpenInCurrentWindowKey];
831     // The meaning of "layout" is defined by the WIN_* defines in main.c.
832     int layout = [ud integerForKey:MMOpenLayoutKey];
833     BOOL splitVert = [ud boolForKey:MMVerticalSplitKey];
834     if (splitVert && MMLayoutHorizontalSplit == layout)
835         layout = MMLayoutVerticalSplit;
836     if (layout < 0 || (layout > MMLayoutTabs && openInCurrentWindow))
837         layout = MMLayoutTabs;
839     [arguments setObject:[NSNumber numberWithInt:layout] forKey:@"layout"];
840     [arguments setObject:filenames forKey:@"filenames"];
841     // (Indicate that files should be opened from now on.)
842     [arguments setObject:[NSNumber numberWithBool:NO] forKey:@"dontOpen"];
844     if (openInCurrentWindow && (vc = [self topmostVimController])) {
845         // Open files in an already open window.
846         [[[vc windowController] window] makeKeyAndOrderFront:self];
847         [vc passArguments:arguments];
848         return YES;
849     }
851     BOOL openOk = YES;
852     int numFiles = [filenames count];
853     if (MMLayoutWindows == layout && numFiles > 1) {
854         // Open one file at a time in a new window, but don't open too many at
855         // once (at most cap+1 windows will open).  If the user has increased
856         // the preload cache size we'll take that as a hint that more windows
857         // should be able to open at once.
858         int cap = [self maxPreloadCacheSize] - 1;
859         if (cap < 4) cap = 4;
860         if (cap > numFiles) cap = numFiles;
862         int i;
863         for (i = 0; i < cap; ++i) {
864             NSArray *a = [NSArray arrayWithObject:[filenames objectAtIndex:i]];
865             [arguments setObject:a forKey:@"filenames"];
867             // NOTE: We have to copy the args since we'll mutate them in the
868             // next loop and the below call may retain the arguments while
869             // waiting for a process to start.
870             NSDictionary *args = [[arguments copy] autorelease];
872             openOk = [self openVimControllerWithArguments:args];
873             if (!openOk) break;
874         }
876         // Open remaining files in tabs in a new window.
877         if (openOk && numFiles > cap) {
878             NSRange range = { i, numFiles-cap };
879             NSArray *a = [filenames subarrayWithRange:range];
880             [arguments setObject:a forKey:@"filenames"];
881             [arguments setObject:[NSNumber numberWithInt:MMLayoutTabs]
882                           forKey:@"layout"];
884             openOk = [self openVimControllerWithArguments:arguments];
885         }
886     } else {
887         // Open all files at once.
888         openOk = [self openVimControllerWithArguments:arguments];
889     }
891     return openOk;
894 #ifdef MM_ENABLE_PLUGINS
895 - (void)addItemToPlugInMenu:(NSMenuItem *)item
897     NSMenu *menu = [plugInMenuItem submenu];
898     [menu addItem:item];
899     if ([menu numberOfItems] == 1)
900         [self addPlugInMenuToMenu:[NSApp mainMenu]];
903 - (void)removeItemFromPlugInMenu:(NSMenuItem *)item
905     NSMenu *menu = [plugInMenuItem submenu];
906     [menu removeItem:item];
907     if ([menu numberOfItems] == 0)
908         [self removePlugInMenu];
910 #endif
912 - (IBAction)newWindow:(id)sender
914     // A cached controller requires no loading times and results in the new
915     // window popping up instantaneously.  If the cache is empty it may take
916     // 1-2 seconds to start a new Vim process.
917     MMVimController *vc = [self takeVimControllerFromCache];
918     if (vc) {
919         [[vc backendProxy] acknowledgeConnection];
920     } else {
921         [self launchVimProcessWithArguments:nil];
922     }
925 - (IBAction)newWindowAndActivate:(id)sender
927     [self activateWhenNextWindowOpens];
928     [self newWindow:sender];
931 - (IBAction)fileOpen:(id)sender
933     NSString *dir = nil;
934     BOOL trackPwd = [[NSUserDefaults standardUserDefaults]
935             boolForKey:MMDialogsTrackPwdKey];
936     if (trackPwd) {
937         MMVimController *vc = [self keyVimController];
938         if (vc) dir = [[vc vimState] objectForKey:@"pwd"];
939     }
941     NSOpenPanel *panel = [NSOpenPanel openPanel];
942     [panel setAllowsMultipleSelection:YES];
943     [panel setAccessoryView:openPanelAccessoryView()];
945     int result = [panel runModalForDirectory:dir file:nil types:nil];
946     if (NSOKButton == result)
947         [self application:NSApp openFiles:[panel filenames]];
950 - (IBAction)selectNextWindow:(id)sender
952     unsigned i, count = [vimControllers count];
953     if (!count) return;
955     NSWindow *keyWindow = [NSApp keyWindow];
956     for (i = 0; i < count; ++i) {
957         MMVimController *vc = [vimControllers objectAtIndex:i];
958         if ([[[vc windowController] window] isEqual:keyWindow])
959             break;
960     }
962     if (i < count) {
963         if (++i >= count)
964             i = 0;
965         MMVimController *vc = [vimControllers objectAtIndex:i];
966         [[vc windowController] showWindow:self];
967     }
970 - (IBAction)selectPreviousWindow:(id)sender
972     unsigned i, count = [vimControllers count];
973     if (!count) return;
975     NSWindow *keyWindow = [NSApp keyWindow];
976     for (i = 0; i < count; ++i) {
977         MMVimController *vc = [vimControllers objectAtIndex:i];
978         if ([[[vc windowController] window] isEqual:keyWindow])
979             break;
980     }
982     if (i < count) {
983         if (i > 0) {
984             --i;
985         } else {
986             i = count - 1;
987         }
988         MMVimController *vc = [vimControllers objectAtIndex:i];
989         [[vc windowController] showWindow:self];
990     }
993 - (IBAction)orderFrontPreferencePanel:(id)sender
995     [[MMPreferenceController sharedPrefsWindowController] showWindow:self];
998 - (IBAction)openWebsite:(id)sender
1000     [[NSWorkspace sharedWorkspace] openURL:
1001             [NSURL URLWithString:MMWebsiteString]];
1004 - (IBAction)showVimHelp:(id)sender
1006     // Open a new window with the help window maximized.
1007     [self launchVimProcessWithArguments:[NSArray arrayWithObjects:
1008             @"-c", @":h gui_mac", @"-c", @":res", nil]];
1011 - (IBAction)zoomAll:(id)sender
1013     [NSApp makeWindowsPerform:@selector(performZoom:) inOrder:YES];
1016 - (IBAction)atsuiButtonClicked:(id)sender
1018     // This action is called when the user clicks the "use ATSUI renderer"
1019     // button in the advanced preferences pane.
1020     [self rebuildPreloadCache];
1023 - (IBAction)loginShellButtonClicked:(id)sender
1025     // This action is called when the user clicks the "use login shell" button
1026     // in the advanced preferences pane.
1027     [self rebuildPreloadCache];
1030 - (IBAction)quickstartButtonClicked:(id)sender
1032     if ([self maxPreloadCacheSize] > 0) {
1033         [self scheduleVimControllerPreloadAfterDelay:1.0];
1034         [self startWatchingVimDir];
1035     } else {
1036         [self cancelVimControllerPreloadRequests];
1037         [self clearPreloadCacheWithCount:-1];
1038         [self stopWatchingVimDir];
1039     }
1042 - (byref id <MMFrontendProtocol>)
1043     connectBackend:(byref in id <MMBackendProtocol>)backend
1044                pid:(int)pid
1046     //NSLog(@"Connect backend (pid=%d)", pid);
1047     NSNumber *pidKey = [NSNumber numberWithInt:pid];
1048     MMVimController *vc = nil;
1050     @try {
1051         [(NSDistantObject*)backend
1052                 setProtocolForProxy:@protocol(MMBackendProtocol)];
1054         vc = [[[MMVimController alloc] initWithBackend:backend pid:pid]
1055                 autorelease];
1057         if (preloadPid == pid) {
1058             // This backend was preloaded, so add it to the cache and schedule
1059             // another vim process to be preloaded.
1060             preloadPid = -1;
1061             [vc setIsPreloading:YES];
1062             [cachedVimControllers addObject:vc];
1063             [self scheduleVimControllerPreloadAfterDelay:1];
1065             return vc;
1066         }
1068         [vimControllers addObject:vc];
1070         id args = [pidArguments objectForKey:pidKey];
1071         if (args && [NSNull null] != args)
1072             [vc passArguments:args];
1074         // HACK!  MacVim does not get activated if it is launched from the
1075         // terminal, so we forcibly activate here unless it is an untitled
1076         // window opening.  Untitled windows are treated differently, else
1077         // MacVim would steal the focus if another app was activated while the
1078         // untitled window was loading.
1079         if (!args || args != [NSNull null])
1080             [NSApp activateIgnoringOtherApps:YES];
1082         if (args)
1083             [pidArguments removeObjectForKey:pidKey];
1085         return vc;
1086     }
1088     @catch (NSException *e) {
1089         NSLog(@"Exception caught in %s: \"%@\"", _cmd, e);
1091         if (vc)
1092             [vimControllers removeObject:vc];
1094         [pidArguments removeObjectForKey:pidKey];
1095     }
1097     return nil;
1100 - (NSArray *)serverList
1102     NSMutableArray *array = [NSMutableArray array];
1104     unsigned i, count = [vimControllers count];
1105     for (i = 0; i < count; ++i) {
1106         MMVimController *controller = [vimControllers objectAtIndex:i];
1107         if ([controller serverName])
1108             [array addObject:[controller serverName]];
1109     }
1111     return array;
1114 - (MMVimController *)keyVimController
1116     NSWindow *keyWindow = [NSApp keyWindow];
1117     if (keyWindow) {
1118         unsigned i, count = [vimControllers count];
1119         for (i = 0; i < count; ++i) {
1120             MMVimController *vc = [vimControllers objectAtIndex:i];
1121             if ([[[vc windowController] window] isEqual:keyWindow])
1122                 return vc;
1123         }
1124     }
1126     return nil;
1129 @end // MMAppController
1134 @implementation MMAppController (MMServices)
1136 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
1137                 error:(NSString **)error
1139     if (![[pboard types] containsObject:NSStringPboardType]) {
1140         NSLog(@"WARNING: Pasteboard contains no object of type "
1141                 "NSStringPboardType");
1142         return;
1143     }
1145     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
1146     BOOL openInCurrentWindow = [ud boolForKey:MMOpenInCurrentWindowKey];
1147     MMVimController *vc;
1149     if (openInCurrentWindow && (vc = [self topmostVimController])) {
1150         [vc sendMessage:AddNewTabMsgID data:nil];
1151         [vc dropString:[pboard stringForType:NSStringPboardType]];
1152     } else {
1153         // Save the text, open a new window, and paste the text when the next
1154         // window opens.  (If this is called several times in a row, then all
1155         // but the last call may be ignored.)
1156         if (openSelectionString) [openSelectionString release];
1157         openSelectionString = [[pboard stringForType:NSStringPboardType] copy];
1159         [self newWindow:self];
1160     }
1163 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
1164            error:(NSString **)error
1166     if (![[pboard types] containsObject:NSStringPboardType]) {
1167         NSLog(@"WARNING: Pasteboard contains no object of type "
1168                 "NSStringPboardType");
1169         return;
1170     }
1172     // TODO: Parse multiple filenames and create array with names.
1173     NSString *string = [pboard stringForType:NSStringPboardType];
1174     string = [string stringByTrimmingCharactersInSet:
1175             [NSCharacterSet whitespaceAndNewlineCharacterSet]];
1176     string = [string stringByStandardizingPath];
1178     NSArray *filenames = [self filterFilesAndNotify:
1179             [NSArray arrayWithObject:string]];
1180     if ([filenames count] == 0)
1181         return;
1183     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
1184     BOOL openInCurrentWindow = [ud boolForKey:MMOpenInCurrentWindowKey];
1185     MMVimController *vc;
1187     if (openInCurrentWindow && (vc = [self topmostVimController])) {
1188         [vc dropFiles:filenames forceOpen:YES];
1189     } else {
1190         [self openFiles:filenames withArguments:nil];
1191     }
1194 - (void)newFileHere:(NSPasteboard *)pboard userData:(NSString *)userData
1195               error:(NSString **)error
1197     if (![[pboard types] containsObject:NSStringPboardType]) {
1198         NSLog(@"WARNING: Pasteboard contains no object of type "
1199               "NSStringPboardType");
1200         return;
1201     }
1203     NSString *path = [pboard stringForType:NSStringPboardType];
1205     BOOL dirIndicator;
1206     if (![[NSFileManager defaultManager] fileExistsAtPath:path
1207                                               isDirectory:&dirIndicator]) {
1208         NSLog(@"Invalid path. Cannot open new document at: %@", path);
1209         return;
1210     }
1212     if (!dirIndicator)
1213         path = [path stringByDeletingLastPathComponent];
1215     path = [path stringByReplacingOccurrencesOfString:@" " withString:@"\\ "];
1217     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
1218     BOOL openInCurrentWindow = [ud boolForKey:MMOpenInCurrentWindowKey];
1219     MMVimController *vc;
1221     if (openInCurrentWindow && (vc = [self topmostVimController])) {
1222         NSString *input = [NSString stringWithFormat:@"<C-\\><C-N>"
1223                 ":tabe|cd %@<CR>", path];
1224         [vc addVimInput:input];
1225     } else {
1226         NSString *input = [NSString stringWithFormat:@":cd %@", path];
1227         [self launchVimProcessWithArguments:[NSArray arrayWithObjects:
1228                                              @"-c", input, nil]];
1229     }
1232 @end // MMAppController (MMServices)
1237 @implementation MMAppController (Private)
1239 - (MMVimController *)topmostVimController
1241     // Find the topmost visible window which has an associated vim controller.
1242     NSEnumerator *e = [[NSApp orderedWindows] objectEnumerator];
1243     id window;
1244     while ((window = [e nextObject]) && [window isVisible]) {
1245         unsigned i, count = [vimControllers count];
1246         for (i = 0; i < count; ++i) {
1247             MMVimController *vc = [vimControllers objectAtIndex:i];
1248             if ([[[vc windowController] window] isEqual:window])
1249                 return vc;
1250         }
1251     }
1253     return nil;
1256 - (int)launchVimProcessWithArguments:(NSArray *)args
1258     int pid = -1;
1259     NSString *path = [[NSBundle mainBundle] pathForAuxiliaryExecutable:@"Vim"];
1261     if (!path) {
1262         NSLog(@"ERROR: Vim executable could not be found inside app bundle!");
1263         return -1;
1264     }
1266     NSArray *taskArgs = [NSArray arrayWithObjects:@"-g", @"-f", nil];
1267     if (args)
1268         taskArgs = [taskArgs arrayByAddingObjectsFromArray:args];
1270     BOOL useLoginShell = [[NSUserDefaults standardUserDefaults]
1271             boolForKey:MMLoginShellKey];
1272     if (useLoginShell) {
1273         // Run process with a login shell, roughly:
1274         //   echo "exec Vim -g -f args" | ARGV0=-`basename $SHELL` $SHELL [-l]
1275         pid = executeInLoginShell(path, taskArgs);
1276     } else {
1277         // Run process directly:
1278         //   Vim -g -f args
1279         NSTask *task = [NSTask launchedTaskWithLaunchPath:path
1280                                                 arguments:taskArgs];
1281         pid = task ? [task processIdentifier] : -1;
1282     }
1284     if (-1 != pid) {
1285         // NOTE: If the process has no arguments, then add a null argument to
1286         // the pidArguments dictionary.  This is later used to detect that a
1287         // process without arguments is being launched.
1288         if (!args)
1289             [pidArguments setObject:[NSNull null]
1290                              forKey:[NSNumber numberWithInt:pid]];
1291     } else {
1292         NSLog(@"WARNING: %s%@ failed (useLoginShell=%d)", _cmd, args,
1293                 useLoginShell);
1294     }
1296     return pid;
1299 - (NSArray *)filterFilesAndNotify:(NSArray *)filenames
1301     // Go trough 'filenames' array and make sure each file exists.  Present
1302     // warning dialog if some file was missing.
1304     NSString *firstMissingFile = nil;
1305     NSMutableArray *files = [NSMutableArray array];
1306     unsigned i, count = [filenames count];
1308     for (i = 0; i < count; ++i) {
1309         NSString *name = [filenames objectAtIndex:i];
1310         if ([[NSFileManager defaultManager] fileExistsAtPath:name]) {
1311             [files addObject:name];
1312         } else if (!firstMissingFile) {
1313             firstMissingFile = name;
1314         }
1315     }
1317     if (firstMissingFile) {
1318         NSAlert *alert = [[NSAlert alloc] init];
1319         [alert addButtonWithTitle:NSLocalizedString(@"OK",
1320                 @"Dialog button")];
1322         NSString *text;
1323         if ([files count] >= count-1) {
1324             [alert setMessageText:NSLocalizedString(@"File not found",
1325                     @"File not found dialog, title")];
1326             text = [NSString stringWithFormat:NSLocalizedString(
1327                     @"Could not open file with name %@.",
1328                     @"File not found dialog, text"), firstMissingFile];
1329         } else {
1330             [alert setMessageText:NSLocalizedString(@"Multiple files not found",
1331                     @"File not found dialog, title")];
1332             text = [NSString stringWithFormat:NSLocalizedString(
1333                     @"Could not open file with name %@, and %d other files.",
1334                     @"File not found dialog, text"),
1335                 firstMissingFile, count-[files count]-1];
1336         }
1338         [alert setInformativeText:text];
1339         [alert setAlertStyle:NSWarningAlertStyle];
1341         [alert runModal];
1342         [alert release];
1344         [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
1345     }
1347     return files;
1350 - (NSArray *)filterOpenFiles:(NSArray *)filenames
1351                openFilesDict:(NSDictionary **)openFiles
1353     // Filter out any files in the 'filenames' array that are open and return
1354     // all files that are not already open.  On return, the 'openFiles'
1355     // parameter (if non-nil) will point to a dictionary of open files, indexed
1356     // by Vim controller.
1358     NSMutableDictionary *dict = [NSMutableDictionary dictionary];
1359     NSMutableArray *files = [filenames mutableCopy];
1361     // TODO: Escape special characters in 'files'?
1362     NSString *expr = [NSString stringWithFormat:
1363             @"map([\"%@\"],\"bufloaded(v:val)\")",
1364             [files componentsJoinedByString:@"\",\""]];
1366     unsigned i, count = [vimControllers count];
1367     for (i = 0; i < count && [files count] > 0; ++i) {
1368         MMVimController *vc = [vimControllers objectAtIndex:i];
1370         // Query Vim for which files in the 'files' array are open.
1371         NSString *eval = [vc evaluateVimExpression:expr];
1372         if (!eval) continue;
1374         NSIndexSet *idxSet = [NSIndexSet indexSetWithVimList:eval];
1375         if ([idxSet count] > 0) {
1376             [dict setObject:[files objectsAtIndexes:idxSet]
1377                      forKey:[NSValue valueWithPointer:vc]];
1379             // Remove all the files that were open in this Vim process and
1380             // create a new expression to evaluate.
1381             [files removeObjectsAtIndexes:idxSet];
1382             expr = [NSString stringWithFormat:
1383                     @"map([\"%@\"],\"bufloaded(v:val)\")",
1384                     [files componentsJoinedByString:@"\",\""]];
1385         }
1386     }
1388     if (openFiles != nil)
1389         *openFiles = dict;
1391     return files;
1394 #if MM_HANDLE_XCODE_MOD_EVENT
1395 - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
1396                  replyEvent:(NSAppleEventDescriptor *)reply
1398 #if 0
1399     // Xcode sends this event to query MacVim which open files have been
1400     // modified.
1401     NSLog(@"reply:%@", reply);
1402     NSLog(@"event:%@", event);
1404     NSEnumerator *e = [vimControllers objectEnumerator];
1405     id vc;
1406     while ((vc = [e nextObject])) {
1407         DescType type = [reply descriptorType];
1408         unsigned len = [[type data] length];
1409         NSMutableData *data = [NSMutableData data];
1411         [data appendBytes:&type length:sizeof(DescType)];
1412         [data appendBytes:&len length:sizeof(unsigned)];
1413         [data appendBytes:[reply data] length:len];
1415         [vc sendMessage:XcodeModMsgID data:data];
1416     }
1417 #endif
1419 #endif
1421 - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
1422                replyEvent:(NSAppleEventDescriptor *)reply
1424     NSString *urlString = [[event paramDescriptorForKeyword:keyDirectObject]
1425         stringValue];
1426     NSURL *url = [NSURL URLWithString:urlString];
1428     // We try to be compatible with TextMate's URL scheme here, as documented
1429     // at http://blog.macromates.com/2007/the-textmate-url-scheme/ . Currently,
1430     // this means that:
1431     //
1432     // The format is: mvim://open?<arguments> where arguments can be:
1433     //
1434     // * url â€” the actual file to open (i.e. a file://… URL), if you leave
1435     //         out this argument, the frontmost document is implied.
1436     // * line â€” line number to go to (one based).
1437     // * column â€” column number to go to (one based).
1438     //
1439     // Example: mvim://open?url=file:///etc/profile&line=20
1441     if ([[url host] isEqualToString:@"open"]) {
1442         NSMutableDictionary *dict = [NSMutableDictionary dictionary];
1444         // Parse query ("url=file://...&line=14") into a dictionary
1445         NSArray *queries = [[url query] componentsSeparatedByString:@"&"];
1446         NSEnumerator *enumerator = [queries objectEnumerator];
1447         NSString *param;
1448         while( param = [enumerator nextObject] ) {
1449             NSArray *arr = [param componentsSeparatedByString:@"="];
1450             if ([arr count] == 2) {
1451                 [dict setValue:[[arr lastObject]
1452                             stringByReplacingPercentEscapesUsingEncoding:
1453                                 NSUTF8StringEncoding]
1454                         forKey:[[arr objectAtIndex:0]
1455                             stringByReplacingPercentEscapesUsingEncoding:
1456                                 NSUTF8StringEncoding]];
1457             }
1458         }
1460         // Actually open the file.
1461         NSString *file = [dict objectForKey:@"url"];
1462         if (file != nil) {
1463             NSURL *fileUrl= [NSURL URLWithString:file];
1464             // TextMate only opens files that already exist.
1465             if ([fileUrl isFileURL]
1466                     && [[NSFileManager defaultManager] fileExistsAtPath:
1467                            [fileUrl path]]) {
1468                 // Strip 'file://' path, else application:openFiles: might think
1469                 // the file is not yet open.
1470                 NSArray *filenames = [NSArray arrayWithObject:[fileUrl path]];
1472                 // Look for the line and column options.
1473                 NSDictionary *args = nil;
1474                 NSString *line = [dict objectForKey:@"line"];
1475                 if (line) {
1476                     NSString *column = [dict objectForKey:@"column"];
1477                     if (column)
1478                         args = [NSDictionary dictionaryWithObjectsAndKeys:
1479                                 line, @"cursorLine",
1480                                 column, @"cursorColumn",
1481                                 nil];
1482                     else
1483                         args = [NSDictionary dictionaryWithObject:line
1484                                 forKey:@"cursorLine"];
1485                 }
1487                 [self openFiles:filenames withArguments:args];
1488             }
1489         }
1490     } else {
1491         NSAlert *alert = [[NSAlert alloc] init];
1492         [alert addButtonWithTitle:NSLocalizedString(@"OK",
1493             @"Dialog button")];
1495         [alert setMessageText:NSLocalizedString(@"Unknown URL Scheme",
1496             @"Unknown URL Scheme dialog, title")];
1497         [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(
1498             @"This version of MacVim does not support \"%@\""
1499             @" in its URL scheme.",
1500             @"Unknown URL Scheme dialog, text"),
1501             [url host]]];
1503         [alert setAlertStyle:NSWarningAlertStyle];
1504         [alert runModal];
1505         [alert release];
1506     }
1510 - (int)findLaunchingProcessWithoutArguments
1512     NSArray *keys = [pidArguments allKeysForObject:[NSNull null]];
1513     if ([keys count] > 0) {
1514         //NSLog(@"found launching process without arguments");
1515         return [[keys objectAtIndex:0] intValue];
1516     }
1518     return -1;
1521 - (MMVimController *)findUnusedEditor
1523     NSEnumerator *e = [vimControllers objectEnumerator];
1524     id vc;
1525     while ((vc = [e nextObject])) {
1526         if ([[[vc vimState] objectForKey:@"unusedEditor"] boolValue])
1527             return vc;
1528     }
1530     return nil;
1533 - (NSMutableDictionary *)extractArgumentsFromOdocEvent:
1534     (NSAppleEventDescriptor *)desc
1536     NSMutableDictionary *dict = [NSMutableDictionary dictionary];
1538     // 1. Extract ODB parameters (if any)
1539     NSAppleEventDescriptor *odbdesc = desc;
1540     if (![odbdesc paramDescriptorForKeyword:keyFileSender]) {
1541         // The ODB paramaters may hide inside the 'keyAEPropData' descriptor.
1542         odbdesc = [odbdesc paramDescriptorForKeyword:keyAEPropData];
1543         if (![odbdesc paramDescriptorForKeyword:keyFileSender])
1544             odbdesc = nil;
1545     }
1547     if (odbdesc) {
1548         NSAppleEventDescriptor *p =
1549                 [odbdesc paramDescriptorForKeyword:keyFileSender];
1550         if (p)
1551             [dict setObject:[NSNumber numberWithUnsignedInt:[p typeCodeValue]]
1552                      forKey:@"remoteID"];
1554         p = [odbdesc paramDescriptorForKeyword:keyFileCustomPath];
1555         if (p)
1556             [dict setObject:[p stringValue] forKey:@"remotePath"];
1558         p = [odbdesc paramDescriptorForKeyword:keyFileSenderToken];
1559         if (p) {
1560             [dict setObject:[NSNumber numberWithUnsignedLong:[p descriptorType]]
1561                      forKey:@"remoteTokenDescType"];
1562             [dict setObject:[p data] forKey:@"remoteTokenData"];
1563         }
1564     }
1566     // 2. Extract Xcode parameters (if any)
1567     NSAppleEventDescriptor *xcodedesc =
1568             [desc paramDescriptorForKeyword:keyAEPosition];
1569     if (xcodedesc) {
1570         NSRange range;
1571         MMSelectionRange *sr = (MMSelectionRange*)[[xcodedesc data] bytes];
1573         if (sr->lineNum < 0) {
1574             // Should select a range of lines.
1575             range.location = sr->startRange + 1;
1576             range.length = sr->endRange - sr->startRange + 1;
1577         } else {
1578             // Should only move cursor to a line.
1579             range.location = sr->lineNum + 1;
1580             range.length = 0;
1581         }
1583         [dict setObject:NSStringFromRange(range) forKey:@"selectionRange"];
1584     }
1586     // 3. Extract Spotlight search text (if any)
1587     NSAppleEventDescriptor *spotlightdesc = 
1588             [desc paramDescriptorForKeyword:keyAESearchText];
1589     if (spotlightdesc)
1590         [dict setObject:[spotlightdesc stringValue] forKey:@"searchText"];
1592     return dict;
1595 #ifdef MM_ENABLE_PLUGINS
1596 - (void)removePlugInMenu
1598     if ([plugInMenuItem menu])
1599         [[plugInMenuItem menu] removeItem:plugInMenuItem];
1602 - (void)addPlugInMenuToMenu:(NSMenu *)mainMenu
1604     NSMenu *windowsMenu = [mainMenu findWindowsMenu];
1606     if ([[plugInMenuItem submenu] numberOfItems] > 0) {
1607         int idx = windowsMenu ? [mainMenu indexOfItemWithSubmenu:windowsMenu]
1608                               : -1;
1609         if (idx > 0) {
1610             [mainMenu insertItem:plugInMenuItem atIndex:idx];
1611         } else {
1612             [mainMenu addItem:plugInMenuItem];
1613         }
1614     }
1616 #endif
1618 - (void)scheduleVimControllerPreloadAfterDelay:(NSTimeInterval)delay
1620     [self performSelector:@selector(preloadVimController:)
1621                withObject:nil
1622                afterDelay:delay];
1625 - (void)cancelVimControllerPreloadRequests
1627     [NSObject cancelPreviousPerformRequestsWithTarget:self
1628             selector:@selector(preloadVimController:)
1629               object:nil];
1632 - (void)preloadVimController:(id)sender
1634     // We only allow preloading of one Vim process at a time (to avoid hogging
1635     // CPU), so schedule another preload in a little while if necessary.
1636     if (-1 != preloadPid) {
1637         [self scheduleVimControllerPreloadAfterDelay:2];
1638         return;
1639     }
1641     if ([cachedVimControllers count] >= [self maxPreloadCacheSize])
1642         return;
1644     preloadPid = [self launchVimProcessWithArguments:
1645             [NSArray arrayWithObject:@"--mmwaitforack"]];
1648 - (int)maxPreloadCacheSize
1650     // The maximum number of Vim processes to keep in the cache can be
1651     // controlled via the user default "MMPreloadCacheSize".
1652     int maxCacheSize = [[NSUserDefaults standardUserDefaults]
1653             integerForKey:MMPreloadCacheSizeKey];
1654     if (maxCacheSize < 0) maxCacheSize = 0;
1655     else if (maxCacheSize > 10) maxCacheSize = 10;
1657     return maxCacheSize;
1660 - (MMVimController *)takeVimControllerFromCache
1662     // NOTE: After calling this message the backend corresponding to the
1663     // returned vim controller must be sent an acknowledgeConnection message,
1664     // else the vim process will be stuck.
1665     //
1666     // This method may return nil even though the cache might be non-empty; the
1667     // caller should handle this by starting a new Vim process.
1669     int i, count = [cachedVimControllers count];
1670     if (0 == count) return nil;
1672     // Locate the first Vim controller with up-to-date rc-files sourced.
1673     NSDate *rcDate = [self rcFilesModificationDate];
1674     for (i = 0; i < count; ++i) {
1675         MMVimController *vc = [cachedVimControllers objectAtIndex:i];
1676         NSDate *date = [vc creationDate];
1677         if ([date compare:rcDate] != NSOrderedAscending)
1678             break;
1679     }
1681     if (i > 0) {
1682         // Clear out cache entries whose vimrc/gvimrc files were sourced before
1683         // the latest modification date for those files.  This ensures that the
1684         // latest rc-files are always sourced for new windows.
1685         [self clearPreloadCacheWithCount:i];
1686     }
1688     if ([cachedVimControllers count] == 0) {
1689         [self scheduleVimControllerPreloadAfterDelay:2.0];
1690         return nil;
1691     }
1693     MMVimController *vc = [cachedVimControllers objectAtIndex:0];
1694     [vimControllers addObject:vc];
1695     [cachedVimControllers removeObjectAtIndex:0];
1696     [vc setIsPreloading:NO];
1698     // If the Vim process has finished loading then the window will displayed
1699     // now, otherwise it will be displayed when the OpenWindowMsgID message is
1700     // received.
1701     [[vc windowController] showWindow];
1703     // Since we've taken one controller from the cache we take the opportunity
1704     // to preload another.
1705     [self scheduleVimControllerPreloadAfterDelay:1];
1707     return vc;
1710 - (void)clearPreloadCacheWithCount:(int)count
1712     // Remove the 'count' first entries in the preload cache.  It is assumed
1713     // that objects are added/removed from the cache in a FIFO manner so that
1714     // this effectively clears the 'count' oldest entries.
1715     // If 'count' is negative, then the entire cache is cleared.
1717     if ([cachedVimControllers count] == 0 || count == 0)
1718         return;
1720     if (count < 0)
1721         count = [cachedVimControllers count];
1723     // Make sure the preloaded Vim processes get killed or they'll just hang
1724     // around being useless until MacVim is terminated.
1725     NSEnumerator *e = [cachedVimControllers objectEnumerator];
1726     MMVimController *vc;
1727     int n = count;
1728     while ((vc = [e nextObject]) && n-- > 0) {
1729         [[NSNotificationCenter defaultCenter] removeObserver:vc];
1730         [vc sendMessage:TerminateNowMsgID data:nil];
1732         // Since the preloaded processes were killed "prematurely" we have to
1733         // manually tell them to cleanup (it is not enough to simply release
1734         // them since deallocation and cleanup are separated).
1735         [vc cleanup];
1736     }
1738     n = count;
1739     while (n-- > 0 && [cachedVimControllers count] > 0)
1740         [cachedVimControllers removeObjectAtIndex:0];
1743 - (void)rebuildPreloadCache
1745     if ([self maxPreloadCacheSize] > 0) {
1746         [self clearPreloadCacheWithCount:-1];
1747         [self cancelVimControllerPreloadRequests];
1748         [self scheduleVimControllerPreloadAfterDelay:1.0];
1749     }
1752 - (NSDate *)rcFilesModificationDate
1754     // Check modification dates for ~/.vimrc and ~/.gvimrc and return the
1755     // latest modification date.  If ~/.vimrc does not exist, check ~/_vimrc
1756     // and similarly for gvimrc.
1757     // Returns distantPath if no rc files were found.
1759     NSDate *date = [NSDate distantPast];
1760     NSFileManager *fm = [NSFileManager defaultManager];
1762     NSString *path = [@"~/.vimrc" stringByExpandingTildeInPath];
1763     NSDictionary *attr = [fm fileAttributesAtPath:path traverseLink:YES];
1764     if (!attr) {
1765         path = [@"~/_vimrc" stringByExpandingTildeInPath];
1766         attr = [fm fileAttributesAtPath:path traverseLink:YES];
1767     }
1768     NSDate *modDate = [attr objectForKey:NSFileModificationDate];
1769     if (modDate)
1770         date = modDate;
1772     path = [@"~/.gvimrc" stringByExpandingTildeInPath];
1773     attr = [fm fileAttributesAtPath:path traverseLink:YES];
1774     if (!attr) {
1775         path = [@"~/_gvimrc" stringByExpandingTildeInPath];
1776         attr = [fm fileAttributesAtPath:path traverseLink:YES];
1777     }
1778     modDate = [attr objectForKey:NSFileModificationDate];
1779     if (modDate)
1780         date = [date laterDate:modDate];
1782     return date;
1785 - (BOOL)openVimControllerWithArguments:(NSDictionary *)arguments
1787     MMVimController *vc = [self findUnusedEditor];
1788     if (vc) {
1789         // Open files in an already open window.
1790         [[[vc windowController] window] makeKeyAndOrderFront:self];
1791         [vc passArguments:arguments];
1792     } else if ((vc = [self takeVimControllerFromCache])) {
1793         // Open files in a new window using a cached vim controller.  This
1794         // requires virtually no loading time so the new window will pop up
1795         // instantaneously.
1796         [vc passArguments:arguments];
1797         [[vc backendProxy] acknowledgeConnection];
1798     } else {
1799         // Open files in a launching Vim process or start a new process.  This
1800         // may take 1-2 seconds so there will be a visible delay before the
1801         // window appears on screen.
1802         int pid = [self findLaunchingProcessWithoutArguments];
1803         if (-1 == pid) {
1804             pid = [self launchVimProcessWithArguments:nil];
1805             if (-1 == pid)
1806                 return NO;
1807         }
1809         // TODO: If the Vim process fails to start, or if it changes PID,
1810         // then the memory allocated for these parameters will leak.
1811         // Ensure that this cannot happen or somehow detect it.
1813         if ([arguments count] > 0)
1814             [pidArguments setObject:arguments
1815                              forKey:[NSNumber numberWithInt:pid]];
1816     }
1818     return YES;
1821 - (void)activateWhenNextWindowOpens
1823     shouldActivateWhenNextWindowOpens = YES;
1826 - (void)startWatchingVimDir
1828     //NSLog(@"%s", _cmd);
1829 #if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4)
1830     if (fsEventStream)
1831         return;
1832     if (NULL == FSEventStreamStart)
1833         return; // FSEvent functions are weakly linked
1835     NSString *path = [@"~/.vim" stringByExpandingTildeInPath];
1836     NSArray *pathsToWatch = [NSArray arrayWithObject:path];
1838     fsEventStream = FSEventStreamCreate(NULL, &fsEventCallback, NULL,
1839             (CFArrayRef)pathsToWatch, kFSEventStreamEventIdSinceNow,
1840             MMEventStreamLatency, kFSEventStreamCreateFlagNone);
1842     FSEventStreamScheduleWithRunLoop(fsEventStream,
1843             [[NSRunLoop currentRunLoop] getCFRunLoop],
1844             kCFRunLoopDefaultMode);
1846     FSEventStreamStart(fsEventStream);
1847     //NSLog(@"Started FS event stream");
1848 #endif
1851 - (void)stopWatchingVimDir
1853     //NSLog(@"%s", _cmd);
1854 #if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4)
1855     if (NULL == FSEventStreamStop)
1856         return; // FSEvent functions are weakly linked
1858     if (fsEventStream) {
1859         FSEventStreamStop(fsEventStream);
1860         FSEventStreamInvalidate(fsEventStream);
1861         FSEventStreamRelease(fsEventStream);
1862         fsEventStream = NULL;
1863         //NSLog(@"Stopped FS event stream");
1864     }
1865 #endif
1869 - (void)handleFSEvent
1871     //NSLog(@"%s", _cmd);
1872     [self clearPreloadCacheWithCount:-1];
1874     // Several FS events may arrive in quick succession so make sure to cancel
1875     // any previous preload requests before making a new one.
1876     [self cancelVimControllerPreloadRequests];
1877     [self scheduleVimControllerPreloadAfterDelay:0.5];
1880 @end // MMAppController (Private)
1885     static int
1886 executeInLoginShell(NSString *path, NSArray *args)
1888     // Start a login shell and execute the command 'path' with arguments 'args'
1889     // in the shell.  This ensures that user environment variables are set even
1890     // when MacVim was started from the Finder.
1892     int pid = -1;
1893     NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
1895     // Determine which shell to use to execute the command.  The user
1896     // may decide which shell to use by setting a user default or the
1897     // $SHELL environment variable.
1898     NSString *shell = [ud stringForKey:MMLoginShellCommandKey];
1899     if (!shell || [shell length] == 0)
1900         shell = [[[NSProcessInfo processInfo] environment]
1901             objectForKey:@"SHELL"];
1902     if (!shell)
1903         shell = @"/bin/bash";
1905     //NSLog(@"shell = %@", shell);
1907     // Bash needs the '-l' flag to launch a login shell.  The user may add
1908     // flags by setting a user default.
1909     NSString *shellArgument = [ud stringForKey:MMLoginShellArgumentKey];
1910     if (!shellArgument || [shellArgument length] == 0) {
1911         if ([[shell lastPathComponent] isEqual:@"bash"])
1912             shellArgument = @"-l";
1913         else
1914             shellArgument = nil;
1915     }
1917     //NSLog(@"shellArgument = %@", shellArgument);
1919     // Build input string to pipe to the login shell.
1920     NSMutableString *input = [NSMutableString stringWithFormat:
1921             @"exec \"%@\"", path];
1922     if (args) {
1923         // Append all arguments, making sure they are properly quoted, even
1924         // when they contain single quotes.
1925         NSEnumerator *e = [args objectEnumerator];
1926         id obj;
1928         while ((obj = [e nextObject])) {
1929             NSMutableString *arg = [NSMutableString stringWithString:obj];
1930             [arg replaceOccurrencesOfString:@"'" withString:@"'\"'\"'"
1931                                     options:NSLiteralSearch
1932                                       range:NSMakeRange(0, [arg length])];
1933             [input appendFormat:@" '%@'", arg];
1934         }
1935     }
1937     // Build the argument vector used to start the login shell.
1938     NSString *shellArg0 = [NSString stringWithFormat:@"-%@",
1939              [shell lastPathComponent]];
1940     char *shellArgv[3] = { (char *)[shellArg0 UTF8String], NULL, NULL };
1941     if (shellArgument)
1942         shellArgv[1] = (char *)[shellArgument UTF8String];
1944     // Get the C string representation of the shell path before the fork since
1945     // we must not call Foundation functions after a fork.
1946     const char *shellPath = [shell fileSystemRepresentation];
1948     // Fork and execute the process.
1949     int ds[2];
1950     if (pipe(ds)) return -1;
1952     pid = fork();
1953     if (pid == -1) {
1954         return -1;
1955     } else if (pid == 0) {
1956         // Child process
1958         // We need to undo our zombie avoidance as Vim waits for and needs
1959         // the exit statuses of processes it spawns.
1960         signal(SIGCHLD, SIG_DFL);
1962         if (close(ds[1]) == -1) exit(255);
1963         if (dup2(ds[0], 0) == -1) exit(255);
1965         // Without the following call warning messages like this appear on the
1966         // console:
1967         //     com.apple.launchd[69] : Stray process with PGID equal to this
1968         //                             dead job: PID 1589 PPID 1 Vim
1969         setsid();
1971         execv(shellPath, shellArgv);
1973         // Never reached unless execv fails
1974         exit(255);
1975     } else {
1976         // Parent process
1977         if (close(ds[0]) == -1) return -1;
1979         // Send input to execute to the child process
1980         [input appendString:@"\n"];
1981         int bytes = [input lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1983         if (write(ds[1], [input UTF8String], bytes) != bytes) return -1;
1984         if (close(ds[1]) == -1) return -1;
1985     }
1987     return pid;