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 NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
157 [NSNumber numberWithBool:NO], MMNoWindowKey,
158 [NSNumber numberWithInt:64], MMTabMinWidthKey,
159 [NSNumber numberWithInt:6*64], MMTabMaxWidthKey,
160 [NSNumber numberWithInt:132], MMTabOptimumWidthKey,
161 [NSNumber numberWithBool:YES], MMShowAddTabButtonKey,
162 [NSNumber numberWithInt:2], MMTextInsetLeftKey,
163 [NSNumber numberWithInt:1], MMTextInsetRightKey,
164 [NSNumber numberWithInt:1], MMTextInsetTopKey,
165 [NSNumber numberWithInt:1], MMTextInsetBottomKey,
166 @"MMTypesetter", MMTypesetterKey,
167 [NSNumber numberWithFloat:1], MMCellWidthMultiplierKey,
168 [NSNumber numberWithFloat:-1], MMBaselineOffsetKey,
169 [NSNumber numberWithBool:YES], MMTranslateCtrlClickKey,
170 [NSNumber numberWithInt:0], MMOpenInCurrentWindowKey,
171 [NSNumber numberWithBool:NO], MMNoFontSubstitutionKey,
172 [NSNumber numberWithBool:YES], MMLoginShellKey,
173 [NSNumber numberWithBool:NO], MMAtsuiRendererKey,
174 [NSNumber numberWithInt:MMUntitledWindowAlways],
176 [NSNumber numberWithBool:NO], MMTexturedWindowKey,
177 [NSNumber numberWithBool:NO], MMZoomBothKey,
178 @"", MMLoginShellCommandKey,
179 @"", MMLoginShellArgumentKey,
180 [NSNumber numberWithBool:YES], MMDialogsTrackPwdKey,
181 #ifdef MM_ENABLE_PLUGINS
182 [NSNumber numberWithBool:YES], MMShowLeftPlugInContainerKey,
184 [NSNumber numberWithInt:3], MMOpenLayoutKey,
185 [NSNumber numberWithBool:NO], MMVerticalSplitKey,
186 [NSNumber numberWithInt:0], MMPreloadCacheSizeKey,
187 [NSNumber numberWithInt:0], MMLastWindowClosedBehaviorKey,
188 [NSNumber numberWithBool:YES], MMLoadDefaultFontKey,
191 [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
193 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
194 [NSApp registerServicesMenuSendTypes:types returnTypes:types];
196 // NOTE: Set the current directory to user's home directory, otherwise it
197 // will default to the root directory. (This matters since new Vim
198 // processes inherit MacVim's environment variables.)
199 [[NSFileManager defaultManager] changeCurrentDirectoryPath:
205 if (!(self = [super init])) return nil;
207 [self loadDefaultFont];
209 vimControllers = [NSMutableArray new];
210 cachedVimControllers = [NSMutableArray new];
212 pidArguments = [NSMutableDictionary new];
213 inputQueues = [NSMutableDictionary new];
215 #ifdef MM_ENABLE_PLUGINS
216 NSString *plugInTitle = NSLocalizedString(@"Plug-In",
217 @"Plug-In menu title");
218 plugInMenuItem = [[NSMenuItem alloc] initWithTitle:plugInTitle
221 NSMenu *submenu = [[NSMenu alloc] initWithTitle:plugInTitle];
222 [plugInMenuItem setSubmenu:submenu];
226 // NOTE: Do not use the default connection since the Logitech Control
227 // Center (LCC) input manager steals and this would cause MacVim to
228 // never open any windows. (This is a bug in LCC but since they are
229 // unlikely to fix it, we graciously give them the default connection.)
230 connection = [[NSConnection alloc] initWithReceivePort:[NSPort port]
232 [connection setRootObject:self];
233 [connection setRequestTimeout:MMRequestTimeout];
234 [connection setReplyTimeout:MMReplyTimeout];
236 // NOTE! If the name of the connection changes here it must also be
237 // updated in MMBackend.m.
238 NSString *name = [NSString stringWithFormat:@"%@-connection",
239 [[NSBundle mainBundle] bundlePath]];
240 //NSLog(@"Registering connection with name '%@'", name);
241 if (![connection registerName:name]) {
242 NSLog(@"FATAL ERROR: Failed to register connection with name '%@'",
244 [connection release]; connection = nil;
252 //NSLog(@"MMAppController dealloc");
254 [connection release]; connection = nil;
255 [inputQueues release]; inputQueues = 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;
265 [appMenuItemTemplate release]; appMenuItemTemplate = nil;
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/
288 // http://www.cocoabuilder.com/archive/message/cocoa/2007/8/15/187793
289 // for more information.
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
295 NSMenu *fileMenu = [defaultMainMenu findFileMenu];
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];
310 #if MM_HANDLE_XCODE_MOD_EVENT
311 [[NSAppleEventManager sharedAppleEventManager]
313 andSelector:@selector(handleXcodeModEvent:replyEvent:)
318 // Register 'mvim://' URL handler
319 [[NSAppleEventManager sharedAppleEventManager]
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];
333 if ([self maxPreloadCacheSize] > 0) {
334 [self scheduleVimControllerPreloadAfterDelay:2];
335 [self startWatchingVimDir];
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];
349 BOOL isAppOpenEvent = [desc eventID] == kAEOpenApplication;
350 if (isAppOpenEvent && (untitledWindowFlag & MMUntitledWindowOnOpen) == 0)
353 BOOL isAppReopenEvent = [desc eventID] == kAEReopenApplication;
355 && (untitledWindowFlag & MMUntitledWindowOnReopen) == 0)
358 // When a process is started from the command line, the 'Open' event may
359 // contain a parameter to surpress the opening of an untitled window.
360 desc = [desc paramDescriptorForKeyword:keyAEPropData];
361 desc = [desc paramDescriptorForKeyword:keyMMUntitledWindow];
362 if (desc && ![desc booleanValue])
365 // Never open an untitled window if there is at least one open window or if
366 // there are processes that are currently launching.
367 if ([vimControllers count] > 0 || [pidArguments count] > 0)
370 // NOTE! This way it possible to start the app with the command-line
371 // argument '-nowindow yes' and no window will be opened by default but
372 // this argument will only be heeded when the application is opening.
373 if (isAppOpenEvent && [ud boolForKey:MMNoWindowKey] == YES)
379 - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
381 [self newWindow:self];
385 - (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
387 // Extract ODB/Xcode/Spotlight parameters from the current Apple event,
388 // sort the filenames, and then let openFiles:withArguments: do the heavy
391 if (!(filenames && [filenames count] > 0))
394 // Sort filenames since the Finder doesn't take care in preserving the
395 // order in which files are selected anyway (and "sorted" is more
396 // predictable than "random").
397 if ([filenames count] > 1)
398 filenames = [filenames sortedArrayUsingSelector:
399 @selector(localizedCompare:)];
401 // Extract ODB/Xcode/Spotlight parameters from the current Apple event
402 NSMutableDictionary *arguments = [self extractArgumentsFromOdocEvent:
403 [[NSAppleEventManager sharedAppleEventManager] currentAppleEvent]];
405 if ([self openFiles:filenames withArguments:arguments]) {
406 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
408 // TODO: Notify user of failure?
409 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
413 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
415 return (MMTerminateWhenLastWindowClosed ==
416 [[NSUserDefaults standardUserDefaults]
417 integerForKey:MMLastWindowClosedBehaviorKey]);
420 - (NSApplicationTerminateReply)applicationShouldTerminate:
421 (NSApplication *)sender
423 // TODO: Follow Apple's guidelines for 'Graceful Application Termination'
424 // (in particular, allow user to review changes and save).
425 int reply = NSTerminateNow;
426 BOOL modifiedBuffers = NO;
428 // Go through windows, checking for modified buffers. (Each Vim process
429 // tells MacVim when any buffer has been modified and MacVim sets the
430 // 'documentEdited' flag of the window correspondingly.)
431 NSEnumerator *e = [[NSApp windows] objectEnumerator];
433 while ((window = [e nextObject])) {
434 if ([window isDocumentEdited]) {
435 modifiedBuffers = YES;
440 if (modifiedBuffers) {
441 NSAlert *alert = [[NSAlert alloc] init];
442 [alert setAlertStyle:NSWarningAlertStyle];
443 [alert addButtonWithTitle:NSLocalizedString(@"Quit",
445 [alert addButtonWithTitle:NSLocalizedString(@"Cancel",
447 [alert setMessageText:NSLocalizedString(@"Quit without saving?",
448 @"Quit dialog with changed buffers, title")];
449 [alert setInformativeText:NSLocalizedString(
450 @"There are modified buffers, "
451 "if you quit now all changes will be lost. Quit anyway?",
452 @"Quit dialog with changed buffers, text")];
454 if ([alert runModal] != NSAlertFirstButtonReturn)
455 reply = NSTerminateCancel;
459 // No unmodified buffers, but give a warning if there are multiple
460 // windows and/or tabs open.
461 int numWindows = [vimControllers count];
464 // Count the number of open tabs
465 e = [vimControllers objectEnumerator];
467 while ((vc = [e nextObject]))
468 numTabs += [[vc objectForVimStateKey:@"numTabs"] intValue];
470 if (numWindows > 1 || numTabs > 1) {
471 NSAlert *alert = [[NSAlert alloc] init];
472 [alert setAlertStyle:NSWarningAlertStyle];
473 [alert addButtonWithTitle:NSLocalizedString(@"Quit",
475 [alert addButtonWithTitle:NSLocalizedString(@"Cancel",
477 [alert setMessageText:NSLocalizedString(
478 @"Are you sure you want to quit MacVim?",
479 @"Quit dialog with no changed buffers, title")];
481 NSString *info = nil;
482 if (numWindows > 1) {
483 if (numTabs > numWindows)
484 info = [NSString stringWithFormat:NSLocalizedString(
485 @"There are %d windows open in MacVim, with a "
486 "total of %d tabs. Do you want to quit anyway?",
487 @"Quit dialog with no changed buffers, text"),
488 numWindows, numTabs];
490 info = [NSString stringWithFormat:NSLocalizedString(
491 @"There are %d windows open in MacVim. "
492 "Do you want to quit anyway?",
493 @"Quit dialog with no changed buffers, text"),
497 info = [NSString stringWithFormat:NSLocalizedString(
498 @"There are %d tabs open in MacVim. "
499 "Do you want to quit anyway?",
500 @"Quit dialog with no changed buffers, text"),
504 [alert setInformativeText:info];
506 if ([alert runModal] != NSAlertFirstButtonReturn)
507 reply = NSTerminateCancel;
514 // Tell all Vim processes to terminate now (otherwise they'll leave swap
516 if (NSTerminateNow == reply) {
517 e = [vimControllers objectEnumerator];
519 while ((vc = [e nextObject])) {
520 //NSLog(@"Terminate pid=%d", [vc pid]);
521 [vc sendMessage:TerminateNowMsgID data:nil];
524 e = [cachedVimControllers objectEnumerator];
525 while ((vc = [e nextObject])) {
526 //NSLog(@"Terminate pid=%d (cached)", [vc pid]);
527 [vc sendMessage:TerminateNowMsgID data:nil];
530 // If a Vim process is being preloaded as we quit we have to forcibly
531 // kill it since we have not established a connection yet.
532 if (preloadPid > 0) {
533 //NSLog(@"INCOMPLETE preloaded process: preloadPid=%d", preloadPid);
534 kill(preloadPid, SIGKILL);
537 // If a Vim process was loading as we quit we also have to kill it.
538 e = [[pidArguments allKeys] objectEnumerator];
540 while ((pidKey = [e nextObject])) {
541 //NSLog(@"INCOMPLETE process: pid=%d", [pidKey intValue]);
542 kill([pidKey intValue], SIGKILL);
545 // Sleep a little to allow all the Vim processes to exit.
552 - (void)applicationWillTerminate:(NSNotification *)notification
554 [self stopWatchingVimDir];
556 #ifdef MM_ENABLE_PLUGINS
557 [[MMPlugInManager sharedManager] unloadAllPlugIns];
560 #if MM_HANDLE_XCODE_MOD_EVENT
561 [[NSAppleEventManager sharedAppleEventManager]
562 removeEventHandlerForEventClass:'KAHL'
566 // This will invalidate all connections (since they were spawned from this
568 [connection invalidate];
570 // Deactivate the font we loaded from the app bundle.
571 // NOTE: This can take quite a while (~500 ms), so termination will be
572 // noticeably faster if loading of the default font is disabled.
573 if (fontContainerRef) {
574 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
575 fontContainerRef = 0;
578 [NSApp setDelegate:nil];
580 // Try to wait for all child processes to avoid leaving zombies behind (but
581 // don't wait around for too long).
582 NSDate *timeOutDate = [NSDate dateWithTimeIntervalSinceNow:2];
583 while ([timeOutDate timeIntervalSinceNow] > 0) {
584 [self reapChildProcesses:nil];
585 if (numChildProcesses <= 0)
588 //NSLog(@"%d processes still left, sleep a bit...", numChildProcesses);
590 // Run in NSConnectionReplyMode while waiting instead of calling e.g.
591 // usleep(). Otherwise incoming messages may clog up the DO queues and
592 // the outgoing TerminateNowMsgID sent earlier never reaches the Vim
594 // This has at least one side-effect, namely we may receive the
595 // annoying "dropping incoming DO message". (E.g. this may happen if
596 // you quickly hit Cmd-n several times in a row and then immediately
597 // press Cmd-q, Enter.)
598 while (CFRunLoopRunInMode((CFStringRef)NSConnectionReplyMode,
599 0.05, true) == kCFRunLoopRunHandledSource)
603 if (numChildProcesses > 0)
604 NSLog(@"%d ZOMBIES left behind", numChildProcesses);
607 + (MMAppController *)sharedInstance
609 // Note: The app controller is a singleton which is instantiated in
610 // MainMenu.nib where it is also connected as the delegate of NSApp.
611 id delegate = [NSApp delegate];
612 return [delegate isKindOfClass:self] ? (MMAppController*)delegate : nil;
615 - (NSMenu *)defaultMainMenu
617 return defaultMainMenu;
620 - (NSMenuItem *)appMenuItemTemplate
622 return appMenuItemTemplate;
625 - (void)removeVimController:(id)controller
627 int idx = [vimControllers indexOfObject:controller];
628 if (NSNotFound == idx)
631 [controller cleanup];
633 [vimControllers removeObjectAtIndex:idx];
635 if (![vimControllers count]) {
636 // The last editor window just closed so restore the main menu back to
637 // its default state (which is defined in MainMenu.nib).
638 [self setMainMenu:defaultMainMenu];
640 BOOL hide = (MMHideWhenLastWindowClosed ==
641 [[NSUserDefaults standardUserDefaults]
642 integerForKey:MMLastWindowClosedBehaviorKey]);
647 // There is a small delay before the Vim process actually exits so wait a
648 // little before trying to reap the child process. If the process still
649 // hasn't exited after this wait it won't be reaped until the next time
650 // reapChildProcesses: is called (but this should be harmless).
651 [self performSelector:@selector(reapChildProcesses:)
656 - (void)windowControllerWillOpen:(MMWindowController *)windowController
658 NSPoint topLeft = NSZeroPoint;
659 NSWindow *topWin = [[[self topmostVimController] windowController] window];
660 NSWindow *win = [windowController window];
664 // If there is a window belonging to a Vim process, cascade from it,
665 // otherwise use the autosaved window position (if any).
667 NSRect frame = [topWin frame];
668 topLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
670 NSString *topLeftString = [[NSUserDefaults standardUserDefaults]
671 stringForKey:MMTopLeftPointKey];
673 topLeft = NSPointFromString(topLeftString);
676 if (!NSEqualPoints(topLeft, NSZeroPoint)) {
677 NSPoint oldTopLeft = topLeft;
679 topLeft = [win cascadeTopLeftFromPoint:topLeft];
681 [win setFrameTopLeftPoint:topLeft];
684 NSPoint screenOrigin = [[win screen] frame].origin;
685 if ([win frame].origin.y < screenOrigin.y) {
686 // Try to avoid shifting the new window downwards if it means
687 // that the bottom of the window will be off the screen. E.g.
688 // if the user has set windows to open maximized in the
689 // vertical direction then the new window will cascade
690 // horizontally only.
691 topLeft.y = oldTopLeft.y;
692 [win setFrameTopLeftPoint:topLeft];
695 if ([win frame].origin.y < screenOrigin.y) {
696 // Move the window to the top of the screen if the bottom of
697 // the window is still obscured.
698 topLeft.y = NSMaxY([[win screen] frame]);
699 [win setFrameTopLeftPoint:topLeft];
702 NSLog(@"[%s] WINDOW NOT ON SCREEN, don't constrain position", _cmd);
706 if (1 == [vimControllers count]) {
707 // The first window autosaves its position. (The autosaving
708 // features of Cocoa are not used because we need more control over
709 // what is autosaved and when it is restored.)
710 [windowController setWindowAutosaveKey:MMTopLeftPointKey];
713 if (openSelectionString) {
714 // TODO: Pass this as a parameter instead! Get rid of
715 // 'openSelectionString' etc.
717 // There is some text to paste into this window as a result of the
718 // services menu "Open selection ..." being used.
719 [[windowController vimController] dropString:openSelectionString];
720 [openSelectionString release];
721 openSelectionString = nil;
724 if (shouldActivateWhenNextWindowOpens) {
725 [NSApp activateIgnoringOtherApps:YES];
726 shouldActivateWhenNextWindowOpens = NO;
730 - (void)setMainMenu:(NSMenu *)mainMenu
732 if ([NSApp mainMenu] == mainMenu) return;
734 // If the new menu has a "Recent Files" dummy item, then swap the real item
735 // for the dummy. We are forced to do this since Cocoa initializes the
736 // "Recent Files" menu and there is no way to simply point Cocoa to a new
737 // item each time the menus are swapped.
738 NSMenu *fileMenu = [mainMenu findFileMenu];
739 if (recentFilesMenuItem && fileMenu) {
741 [fileMenu indexOfItemWithAction:@selector(recentFilesDummy:)];
743 NSMenuItem *dummyItem = [[fileMenu itemAtIndex:dummyIdx] retain];
744 [fileMenu removeItemAtIndex:dummyIdx];
746 NSMenu *recentFilesParentMenu = [recentFilesMenuItem menu];
747 int idx = [recentFilesParentMenu indexOfItem:recentFilesMenuItem];
749 [[recentFilesMenuItem retain] autorelease];
750 [recentFilesParentMenu removeItemAtIndex:idx];
751 [recentFilesParentMenu insertItem:dummyItem atIndex:idx];
754 [fileMenu insertItem:recentFilesMenuItem atIndex:dummyIdx];
759 // Now set the new menu. Notice that we keep one menu for each editor
760 // window since each editor can have its own set of menus. When swapping
761 // menus we have to tell Cocoa where the new "MacVim", "Windows", and
762 // "Services" menu are.
763 [NSApp setMainMenu:mainMenu];
765 // Setting the "MacVim" (or "Application") menu ensures that it is typeset
766 // in boldface. (The setAppleMenu: method used to be public but is now
767 // private so this will have to be considered a bit of a hack!)
768 NSMenu *appMenu = [mainMenu findApplicationMenu];
769 [NSApp performSelector:@selector(setAppleMenu:) withObject:appMenu];
771 NSMenu *servicesMenu = [mainMenu findServicesMenu];
772 [NSApp setServicesMenu:servicesMenu];
774 NSMenu *windowsMenu = [mainMenu findWindowsMenu];
776 // Cocoa isn't clever enough to get rid of items it has added to the
777 // "Windows" menu so we have to do it ourselves otherwise there will be
778 // multiple menu items for each window in the "Windows" menu.
779 // This code assumes that the only items Cocoa add are ones which
780 // send off the action makeKeyAndOrderFront:. (Cocoa will not add
781 // another separator item if the last item on the "Windows" menu
782 // already is a separator, so we needen't worry about separators.)
783 int i, count = [windowsMenu numberOfItems];
784 for (i = count-1; i >= 0; --i) {
785 NSMenuItem *item = [windowsMenu itemAtIndex:i];
786 if ([item action] == @selector(makeKeyAndOrderFront:))
787 [windowsMenu removeItem:item];
790 [NSApp setWindowsMenu:windowsMenu];
792 #ifdef MM_ENABLE_PLUGINS
793 // Move plugin menu from old to new main menu.
794 [self removePlugInMenu];
795 [self addPlugInMenuToMenu:mainMenu];
799 - (NSArray *)filterOpenFiles:(NSArray *)filenames
801 return [self filterOpenFiles:filenames openFilesDict:nil];
804 - (BOOL)openFiles:(NSArray *)filenames withArguments:(NSDictionary *)args
806 // Opening files works like this:
807 // a) filter out any already open files
808 // b) open any remaining files
810 // A file is opened in an untitled window if there is one (it may be
811 // currently launching, or it may already be visible), otherwise a new
814 // Each launching Vim process has a dictionary of arguments that are passed
815 // to the process when in checks in (via connectBackend:pid:). The
816 // arguments for each launching process can be looked up by its PID (in the
817 // pidArguments dictionary).
819 NSMutableDictionary *arguments = (args ? [[args mutableCopy] autorelease]
820 : [NSMutableDictionary dictionary]);
823 // a) Filter out any already open files
825 NSString *firstFile = [filenames objectAtIndex:0];
826 MMVimController *firstController = nil;
827 NSDictionary *openFilesDict = nil;
828 filenames = [self filterOpenFiles:filenames openFilesDict:&openFilesDict];
830 // Pass arguments to vim controllers that had files open.
832 NSEnumerator *e = [openFilesDict keyEnumerator];
834 // (Indicate that we do not wish to open any files at the moment.)
835 [arguments setObject:[NSNumber numberWithBool:YES] forKey:@"dontOpen"];
837 while ((key = [e nextObject])) {
838 NSArray *files = [openFilesDict objectForKey:key];
839 [arguments setObject:files forKey:@"filenames"];
841 MMVimController *vc = [key pointerValue];
842 [vc passArguments:arguments];
844 // If this controller holds the first file, then remember it for later.
845 if ([files containsObject:firstFile])
846 firstController = vc;
849 // The meaning of "layout" is defined by the WIN_* defines in main.c.
850 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
851 int layout = [ud integerForKey:MMOpenLayoutKey];
852 BOOL splitVert = [ud boolForKey:MMVerticalSplitKey];
853 BOOL openInCurrentWindow = [ud boolForKey:MMOpenInCurrentWindowKey];
855 if (splitVert && MMLayoutHorizontalSplit == layout)
856 layout = MMLayoutVerticalSplit;
857 if (layout < 0 || (layout > MMLayoutTabs && openInCurrentWindow))
858 layout = MMLayoutTabs;
860 if ([filenames count] == 0) {
861 // Raise the window containing the first file that was already open,
862 // and make sure that the tab containing that file is selected. Only
863 // do this when there are no more files to open, otherwise sometimes
864 // the window with 'firstFile' will be raised, other times it might be
865 // the window that will open with the files in the 'filenames' array.
866 firstFile = [firstFile stringByEscapingSpecialFilenameCharacters];
868 NSString *bufCmd = @"tab sb";
870 case MMLayoutHorizontalSplit: bufCmd = @"sb"; break;
871 case MMLayoutVerticalSplit: bufCmd = @"vert sb"; break;
872 case MMLayoutArglist: bufCmd = @"b"; break;
875 NSString *input = [NSString stringWithFormat:@"<C-\\><C-N>"
876 ":let oldswb=&swb|let &swb=\"useopen,usetab\"|"
877 "%@ %@|let &swb=oldswb|unl oldswb|"
878 "cal foreground()<CR>", bufCmd, firstFile];
880 [firstController addVimInput:input];
885 // Add filenames to "Recent Files" menu, unless they are being edited
886 // remotely (using ODB).
887 if ([arguments objectForKey:@"remoteID"] == nil) {
888 [[NSDocumentController sharedDocumentController]
889 noteNewRecentFilePaths:filenames];
893 // b) Open any remaining files
896 [arguments setObject:[NSNumber numberWithInt:layout] forKey:@"layout"];
897 [arguments setObject:filenames forKey:@"filenames"];
898 // (Indicate that files should be opened from now on.)
899 [arguments setObject:[NSNumber numberWithBool:NO] forKey:@"dontOpen"];
902 if (openInCurrentWindow && (vc = [self topmostVimController])) {
903 // Open files in an already open window.
904 [[[vc windowController] window] makeKeyAndOrderFront:self];
905 [vc passArguments:arguments];
910 int numFiles = [filenames count];
911 if (MMLayoutWindows == layout && numFiles > 1) {
912 // Open one file at a time in a new window, but don't open too many at
913 // once (at most cap+1 windows will open). If the user has increased
914 // the preload cache size we'll take that as a hint that more windows
915 // should be able to open at once.
916 int cap = [self maxPreloadCacheSize] - 1;
917 if (cap < 4) cap = 4;
918 if (cap > numFiles) cap = numFiles;
921 for (i = 0; i < cap; ++i) {
922 NSArray *a = [NSArray arrayWithObject:[filenames objectAtIndex:i]];
923 [arguments setObject:a forKey:@"filenames"];
925 // NOTE: We have to copy the args since we'll mutate them in the
926 // next loop and the below call may retain the arguments while
927 // waiting for a process to start.
928 NSDictionary *args = [[arguments copy] autorelease];
930 openOk = [self openVimControllerWithArguments:args];
934 // Open remaining files in tabs in a new window.
935 if (openOk && numFiles > cap) {
936 NSRange range = { i, numFiles-cap };
937 NSArray *a = [filenames subarrayWithRange:range];
938 [arguments setObject:a forKey:@"filenames"];
939 [arguments setObject:[NSNumber numberWithInt:MMLayoutTabs]
942 openOk = [self openVimControllerWithArguments:arguments];
945 // Open all files at once.
946 openOk = [self openVimControllerWithArguments:arguments];
952 #ifdef MM_ENABLE_PLUGINS
953 - (void)addItemToPlugInMenu:(NSMenuItem *)item
955 NSMenu *menu = [plugInMenuItem submenu];
957 if ([menu numberOfItems] == 1)
958 [self addPlugInMenuToMenu:[NSApp mainMenu]];
961 - (void)removeItemFromPlugInMenu:(NSMenuItem *)item
963 NSMenu *menu = [plugInMenuItem submenu];
964 [menu removeItem:item];
965 if ([menu numberOfItems] == 0)
966 [self removePlugInMenu];
970 - (IBAction)newWindow:(id)sender
972 // A cached controller requires no loading times and results in the new
973 // window popping up instantaneously. If the cache is empty it may take
974 // 1-2 seconds to start a new Vim process.
975 MMVimController *vc = [self takeVimControllerFromCache];
977 [[vc backendProxy] acknowledgeConnection];
979 [self launchVimProcessWithArguments:nil];
983 - (IBAction)newWindowAndActivate:(id)sender
985 [self activateWhenNextWindowOpens];
986 [self newWindow:sender];
989 - (IBAction)fileOpen:(id)sender
992 BOOL trackPwd = [[NSUserDefaults standardUserDefaults]
993 boolForKey:MMDialogsTrackPwdKey];
995 MMVimController *vc = [self keyVimController];
996 if (vc) dir = [vc objectForVimStateKey:@"pwd"];
999 NSOpenPanel *panel = [NSOpenPanel openPanel];
1000 [panel setAllowsMultipleSelection:YES];
1001 [panel setAccessoryView:showHiddenFilesView()];
1003 int result = [panel runModalForDirectory:dir file:nil types:nil];
1004 if (NSOKButton == result)
1005 [self application:NSApp openFiles:[panel filenames]];
1008 - (IBAction)selectNextWindow:(id)sender
1010 unsigned i, count = [vimControllers count];
1013 NSWindow *keyWindow = [NSApp keyWindow];
1014 for (i = 0; i < count; ++i) {
1015 MMVimController *vc = [vimControllers objectAtIndex:i];
1016 if ([[[vc windowController] window] isEqual:keyWindow])
1023 MMVimController *vc = [vimControllers objectAtIndex:i];
1024 [[vc windowController] showWindow:self];
1028 - (IBAction)selectPreviousWindow:(id)sender
1030 unsigned i, count = [vimControllers count];
1033 NSWindow *keyWindow = [NSApp keyWindow];
1034 for (i = 0; i < count; ++i) {
1035 MMVimController *vc = [vimControllers objectAtIndex:i];
1036 if ([[[vc windowController] window] isEqual:keyWindow])
1046 MMVimController *vc = [vimControllers objectAtIndex:i];
1047 [[vc windowController] showWindow:self];
1051 - (IBAction)orderFrontPreferencePanel:(id)sender
1053 [[MMPreferenceController sharedPrefsWindowController] showWindow:self];
1056 - (IBAction)openWebsite:(id)sender
1058 [[NSWorkspace sharedWorkspace] openURL:
1059 [NSURL URLWithString:MMWebsiteString]];
1062 - (IBAction)showVimHelp:(id)sender
1064 // Open a new window with the help window maximized.
1065 [self launchVimProcessWithArguments:[NSArray arrayWithObjects:
1066 @"-c", @":h gui_mac", @"-c", @":res", nil]];
1069 - (IBAction)zoomAll:(id)sender
1071 [NSApp makeWindowsPerform:@selector(performZoom:) inOrder:YES];
1074 - (IBAction)atsuiButtonClicked:(id)sender
1076 // This action is called when the user clicks the "use ATSUI renderer"
1077 // button in the advanced preferences pane.
1078 [self rebuildPreloadCache];
1081 - (IBAction)loginShellButtonClicked:(id)sender
1083 // This action is called when the user clicks the "use login shell" button
1084 // in the advanced preferences pane.
1085 [self rebuildPreloadCache];
1088 - (IBAction)quickstartButtonClicked:(id)sender
1090 if ([self maxPreloadCacheSize] > 0) {
1091 [self scheduleVimControllerPreloadAfterDelay:1.0];
1092 [self startWatchingVimDir];
1094 [self cancelVimControllerPreloadRequests];
1095 [self clearPreloadCacheWithCount:-1];
1096 [self stopWatchingVimDir];
1100 - (MMVimController *)keyVimController
1102 NSWindow *keyWindow = [NSApp keyWindow];
1104 unsigned i, count = [vimControllers count];
1105 for (i = 0; i < count; ++i) {
1106 MMVimController *vc = [vimControllers objectAtIndex:i];
1107 if ([[[vc windowController] window] isEqual:keyWindow])
1115 - (unsigned)connectBackend:(byref in id <MMBackendProtocol>)proxy pid:(int)pid
1117 //NSLog(@"[%s] pid=%d", _cmd, pid);
1119 [(NSDistantObject*)proxy setProtocolForProxy:@protocol(MMBackendProtocol)];
1121 // NOTE: Allocate the vim controller now but don't add it to the list of
1122 // controllers since this is a distributed object call and as such can
1123 // arrive at unpredictable times (e.g. while iterating the list of vim
1125 // (What if input arrives before the vim controller is added to the list of
1126 // controllers? This should not be a problem since the input isn't
1127 // processed immediately (see processInput:forIdentifier:).)
1128 MMVimController *vc = [[MMVimController alloc] initWithBackend:proxy
1130 [self performSelector:@selector(addVimController:)
1136 return [vc identifier];
1139 - (oneway void)processInput:(in bycopy NSArray *)queue
1140 forIdentifier:(unsigned)identifier
1142 // NOTE: Input is not handled immediately since this is a distributed
1143 // object call and as such can arrive at unpredictable times. Instead,
1144 // queue the input and process it when the run loop is updated.
1146 if (!(queue && identifier)) {
1147 NSLog(@"[%s] Bad input for identifier=%d", _cmd, identifier);
1151 //NSLog(@"[%s] QUEUE for identifier=%d: <<< %@>>>", _cmd, identifier,
1152 // debugStringForMessageQueue(queue));
1154 NSNumber *key = [NSNumber numberWithUnsignedInt:identifier];
1155 NSArray *q = [inputQueues objectForKey:key];
1157 q = [q arrayByAddingObjectsFromArray:queue];
1158 [inputQueues setObject:q forKey:key];
1160 [inputQueues setObject:queue forKey:key];
1163 // NOTE: We must use "event tracking mode" as well as "default mode",
1164 // otherwise the input queue will not be processed e.g. during live
1166 [self performSelector:@selector(processInputQueues:)
1169 inModes:[NSArray arrayWithObjects:NSDefaultRunLoopMode,
1170 NSEventTrackingRunLoopMode, nil]];
1173 - (NSArray *)serverList
1175 NSMutableArray *array = [NSMutableArray array];
1177 unsigned i, count = [vimControllers count];
1178 for (i = 0; i < count; ++i) {
1179 MMVimController *controller = [vimControllers objectAtIndex:i];
1180 if ([controller serverName])
1181 [array addObject:[controller serverName]];
1187 @end // MMAppController
1192 @implementation MMAppController (MMServices)
1194 - (void)openSelection:(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");
1203 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
1204 BOOL openInCurrentWindow = [ud boolForKey:MMOpenInCurrentWindowKey];
1205 MMVimController *vc;
1207 if (openInCurrentWindow && (vc = [self topmostVimController])) {
1208 [vc sendMessage:AddNewTabMsgID data:nil];
1209 [vc dropString:[pboard stringForType:NSStringPboardType]];
1211 // Save the text, open a new window, and paste the text when the next
1212 // window opens. (If this is called several times in a row, then all
1213 // but the last call may be ignored.)
1214 if (openSelectionString) [openSelectionString release];
1215 openSelectionString = [[pboard stringForType:NSStringPboardType] copy];
1217 [self newWindow:self];
1221 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
1222 error:(NSString **)error
1224 if (![[pboard types] containsObject:NSStringPboardType]) {
1225 NSLog(@"WARNING: Pasteboard contains no object of type "
1226 "NSStringPboardType");
1230 // TODO: Parse multiple filenames and create array with names.
1231 NSString *string = [pboard stringForType:NSStringPboardType];
1232 string = [string stringByTrimmingCharactersInSet:
1233 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
1234 string = [string stringByStandardizingPath];
1236 NSArray *filenames = [self filterFilesAndNotify:
1237 [NSArray arrayWithObject:string]];
1238 if ([filenames count] == 0)
1241 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
1242 BOOL openInCurrentWindow = [ud boolForKey:MMOpenInCurrentWindowKey];
1243 MMVimController *vc;
1245 if (openInCurrentWindow && (vc = [self topmostVimController])) {
1246 [vc dropFiles:filenames forceOpen:YES];
1248 [self openFiles:filenames withArguments:nil];
1252 - (void)newFileHere:(NSPasteboard *)pboard userData:(NSString *)userData
1253 error:(NSString **)error
1255 if (![[pboard types] containsObject:NSStringPboardType]) {
1256 NSLog(@"WARNING: Pasteboard contains no object of type "
1257 "NSStringPboardType");
1261 NSString *path = [pboard stringForType:NSStringPboardType];
1264 if (![[NSFileManager defaultManager] fileExistsAtPath:path
1265 isDirectory:&dirIndicator]) {
1266 NSLog(@"Invalid path. Cannot open new document at: %@", path);
1271 path = [path stringByDeletingLastPathComponent];
1273 path = [path stringByEscapingSpecialFilenameCharacters];
1275 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
1276 BOOL openInCurrentWindow = [ud boolForKey:MMOpenInCurrentWindowKey];
1277 MMVimController *vc;
1279 if (openInCurrentWindow && (vc = [self topmostVimController])) {
1280 NSString *input = [NSString stringWithFormat:@"<C-\\><C-N>"
1281 ":tabe|cd %@<CR>", path];
1282 [vc addVimInput:input];
1284 NSString *input = [NSString stringWithFormat:@":cd %@", path];
1285 [self launchVimProcessWithArguments:[NSArray arrayWithObjects:
1286 @"-c", input, nil]];
1290 @end // MMAppController (MMServices)
1295 @implementation MMAppController (Private)
1297 - (MMVimController *)topmostVimController
1299 // Find the topmost visible window which has an associated vim controller.
1300 NSEnumerator *e = [[NSApp orderedWindows] objectEnumerator];
1302 while ((window = [e nextObject]) && [window isVisible]) {
1303 unsigned i, count = [vimControllers count];
1304 for (i = 0; i < count; ++i) {
1305 MMVimController *vc = [vimControllers objectAtIndex:i];
1306 if ([[[vc windowController] window] isEqual:window])
1314 - (int)launchVimProcessWithArguments:(NSArray *)args
1317 NSString *path = [[NSBundle mainBundle] pathForAuxiliaryExecutable:@"Vim"];
1320 NSLog(@"ERROR: Vim executable could not be found inside app bundle!");
1324 NSArray *taskArgs = [NSArray arrayWithObjects:@"-g", @"-f", nil];
1326 taskArgs = [taskArgs arrayByAddingObjectsFromArray:args];
1328 BOOL useLoginShell = [[NSUserDefaults standardUserDefaults]
1329 boolForKey:MMLoginShellKey];
1330 if (useLoginShell) {
1331 // Run process with a login shell, roughly:
1332 // echo "exec Vim -g -f args" | ARGV0=-`basename $SHELL` $SHELL [-l]
1333 pid = [self executeInLoginShell:path arguments:taskArgs];
1335 // Run process directly:
1337 NSTask *task = [NSTask launchedTaskWithLaunchPath:path
1338 arguments:taskArgs];
1339 pid = task ? [task processIdentifier] : -1;
1343 // The 'pidArguments' dictionary keeps arguments to be passed to the
1344 // process when it connects (this is in contrast to arguments which are
1345 // passed on the command line, like '-f' and '-g').
1346 // If this method is called with nil arguments we take this as a hint
1347 // that this is an "untitled window" being launched and add a null
1348 // object to the 'pidArguments' dictionary. This way we can detect if
1349 // an untitled window is being launched by looking for null objects in
1351 // If this method is called with non-nil arguments then it is assumed
1352 // that the caller takes care of adding items to 'pidArguments' as
1353 // necessary (only some arguments are passed on connect, e.g. files to
1356 [pidArguments setObject:[NSNull null]
1357 forKey:[NSNumber numberWithInt:pid]];
1359 NSLog(@"WARNING: %s%@ failed (useLoginShell=%d)", _cmd, args,
1366 - (NSArray *)filterFilesAndNotify:(NSArray *)filenames
1368 // Go trough 'filenames' array and make sure each file exists. Present
1369 // warning dialog if some file was missing.
1371 NSString *firstMissingFile = nil;
1372 NSMutableArray *files = [NSMutableArray array];
1373 unsigned i, count = [filenames count];
1375 for (i = 0; i < count; ++i) {
1376 NSString *name = [filenames objectAtIndex:i];
1377 if ([[NSFileManager defaultManager] fileExistsAtPath:name]) {
1378 [files addObject:name];
1379 } else if (!firstMissingFile) {
1380 firstMissingFile = name;
1384 if (firstMissingFile) {
1385 NSAlert *alert = [[NSAlert alloc] init];
1386 [alert addButtonWithTitle:NSLocalizedString(@"OK",
1390 if ([files count] >= count-1) {
1391 [alert setMessageText:NSLocalizedString(@"File not found",
1392 @"File not found dialog, title")];
1393 text = [NSString stringWithFormat:NSLocalizedString(
1394 @"Could not open file with name %@.",
1395 @"File not found dialog, text"), firstMissingFile];
1397 [alert setMessageText:NSLocalizedString(@"Multiple files not found",
1398 @"File not found dialog, title")];
1399 text = [NSString stringWithFormat:NSLocalizedString(
1400 @"Could not open file with name %@, and %d other files.",
1401 @"File not found dialog, text"),
1402 firstMissingFile, count-[files count]-1];
1405 [alert setInformativeText:text];
1406 [alert setAlertStyle:NSWarningAlertStyle];
1411 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
1417 - (NSArray *)filterOpenFiles:(NSArray *)filenames
1418 openFilesDict:(NSDictionary **)openFiles
1420 // Filter out any files in the 'filenames' array that are open and return
1421 // all files that are not already open. On return, the 'openFiles'
1422 // parameter (if non-nil) will point to a dictionary of open files, indexed
1423 // by Vim controller.
1425 NSMutableDictionary *dict = [NSMutableDictionary dictionary];
1426 NSMutableArray *files = [filenames mutableCopy];
1428 // TODO: Escape special characters in 'files'?
1429 NSString *expr = [NSString stringWithFormat:
1430 @"map([\"%@\"],\"bufloaded(v:val)\")",
1431 [files componentsJoinedByString:@"\",\""]];
1433 unsigned i, count = [vimControllers count];
1434 for (i = 0; i < count && [files count] > 0; ++i) {
1435 MMVimController *vc = [vimControllers objectAtIndex:i];
1437 // Query Vim for which files in the 'files' array are open.
1438 NSString *eval = [vc evaluateVimExpression:expr];
1439 if (!eval) continue;
1441 NSIndexSet *idxSet = [NSIndexSet indexSetWithVimList:eval];
1442 if ([idxSet count] > 0) {
1443 [dict setObject:[files objectsAtIndexes:idxSet]
1444 forKey:[NSValue valueWithPointer:vc]];
1446 // Remove all the files that were open in this Vim process and
1447 // create a new expression to evaluate.
1448 [files removeObjectsAtIndexes:idxSet];
1449 expr = [NSString stringWithFormat:
1450 @"map([\"%@\"],\"bufloaded(v:val)\")",
1451 [files componentsJoinedByString:@"\",\""]];
1455 if (openFiles != nil)
1461 #if MM_HANDLE_XCODE_MOD_EVENT
1462 - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
1463 replyEvent:(NSAppleEventDescriptor *)reply
1466 // Xcode sends this event to query MacVim which open files have been
1468 NSLog(@"reply:%@", reply);
1469 NSLog(@"event:%@", event);
1471 NSEnumerator *e = [vimControllers objectEnumerator];
1473 while ((vc = [e nextObject])) {
1474 DescType type = [reply descriptorType];
1475 unsigned len = [[type data] length];
1476 NSMutableData *data = [NSMutableData data];
1478 [data appendBytes:&type length:sizeof(DescType)];
1479 [data appendBytes:&len length:sizeof(unsigned)];
1480 [data appendBytes:[reply data] length:len];
1482 [vc sendMessage:XcodeModMsgID data:data];
1488 - (void)handleGetURLEvent:(NSAppleEventDescriptor *)event
1489 replyEvent:(NSAppleEventDescriptor *)reply
1491 NSString *urlString = [[event paramDescriptorForKeyword:keyDirectObject]
1493 NSURL *url = [NSURL URLWithString:urlString];
1495 // We try to be compatible with TextMate's URL scheme here, as documented
1496 // at http://blog.macromates.com/2007/the-textmate-url-scheme/ . Currently,
1499 // The format is: mvim://open?<arguments> where arguments can be:
1501 // * url — the actual file to open (i.e. a file://… URL), if you leave
1502 // out this argument, the frontmost document is implied.
1503 // * line — line number to go to (one based).
1504 // * column — column number to go to (one based).
1506 // Example: mvim://open?url=file:///etc/profile&line=20
1508 if ([[url host] isEqualToString:@"open"]) {
1509 NSMutableDictionary *dict = [NSMutableDictionary dictionary];
1511 // Parse query ("url=file://...&line=14") into a dictionary
1512 NSArray *queries = [[url query] componentsSeparatedByString:@"&"];
1513 NSEnumerator *enumerator = [queries objectEnumerator];
1515 while( param = [enumerator nextObject] ) {
1516 NSArray *arr = [param componentsSeparatedByString:@"="];
1517 if ([arr count] == 2) {
1518 [dict setValue:[[arr lastObject]
1519 stringByReplacingPercentEscapesUsingEncoding:
1520 NSUTF8StringEncoding]
1521 forKey:[[arr objectAtIndex:0]
1522 stringByReplacingPercentEscapesUsingEncoding:
1523 NSUTF8StringEncoding]];
1527 // Actually open the file.
1528 NSString *file = [dict objectForKey:@"url"];
1530 NSURL *fileUrl= [NSURL URLWithString:file];
1531 // TextMate only opens files that already exist.
1532 if ([fileUrl isFileURL]
1533 && [[NSFileManager defaultManager] fileExistsAtPath:
1535 // Strip 'file://' path, else application:openFiles: might think
1536 // the file is not yet open.
1537 NSArray *filenames = [NSArray arrayWithObject:[fileUrl path]];
1539 // Look for the line and column options.
1540 NSDictionary *args = nil;
1541 NSString *line = [dict objectForKey:@"line"];
1543 NSString *column = [dict objectForKey:@"column"];
1545 args = [NSDictionary dictionaryWithObjectsAndKeys:
1546 line, @"cursorLine",
1547 column, @"cursorColumn",
1550 args = [NSDictionary dictionaryWithObject:line
1551 forKey:@"cursorLine"];
1554 [self openFiles:filenames withArguments:args];
1558 NSAlert *alert = [[NSAlert alloc] init];
1559 [alert addButtonWithTitle:NSLocalizedString(@"OK",
1562 [alert setMessageText:NSLocalizedString(@"Unknown URL Scheme",
1563 @"Unknown URL Scheme dialog, title")];
1564 [alert setInformativeText:[NSString stringWithFormat:NSLocalizedString(
1565 @"This version of MacVim does not support \"%@\""
1566 @" in its URL scheme.",
1567 @"Unknown URL Scheme dialog, text"),
1570 [alert setAlertStyle:NSWarningAlertStyle];
1577 - (int)findLaunchingProcessWithoutArguments
1579 NSArray *keys = [pidArguments allKeysForObject:[NSNull null]];
1580 if ([keys count] > 0) {
1581 //NSLog(@"found launching process without arguments");
1582 return [[keys objectAtIndex:0] intValue];
1588 - (MMVimController *)findUnusedEditor
1590 NSEnumerator *e = [vimControllers objectEnumerator];
1592 while ((vc = [e nextObject])) {
1593 if ([[vc objectForVimStateKey:@"unusedEditor"] boolValue])
1600 - (NSMutableDictionary *)extractArgumentsFromOdocEvent:
1601 (NSAppleEventDescriptor *)desc
1603 NSMutableDictionary *dict = [NSMutableDictionary dictionary];
1605 // 1. Extract ODB parameters (if any)
1606 NSAppleEventDescriptor *odbdesc = desc;
1607 if (![odbdesc paramDescriptorForKeyword:keyFileSender]) {
1608 // The ODB paramaters may hide inside the 'keyAEPropData' descriptor.
1609 odbdesc = [odbdesc paramDescriptorForKeyword:keyAEPropData];
1610 if (![odbdesc paramDescriptorForKeyword:keyFileSender])
1615 NSAppleEventDescriptor *p =
1616 [odbdesc paramDescriptorForKeyword:keyFileSender];
1618 [dict setObject:[NSNumber numberWithUnsignedInt:[p typeCodeValue]]
1619 forKey:@"remoteID"];
1621 p = [odbdesc paramDescriptorForKeyword:keyFileCustomPath];
1623 [dict setObject:[p stringValue] forKey:@"remotePath"];
1625 p = [odbdesc paramDescriptorForKeyword:keyFileSenderToken];
1627 [dict setObject:[NSNumber numberWithUnsignedLong:[p descriptorType]]
1628 forKey:@"remoteTokenDescType"];
1629 [dict setObject:[p data] forKey:@"remoteTokenData"];
1633 // 2. Extract Xcode parameters (if any)
1634 NSAppleEventDescriptor *xcodedesc =
1635 [desc paramDescriptorForKeyword:keyAEPosition];
1638 MMSelectionRange *sr = (MMSelectionRange*)[[xcodedesc data] bytes];
1640 if (sr->lineNum < 0) {
1641 // Should select a range of lines.
1642 range.location = sr->startRange + 1;
1643 range.length = sr->endRange - sr->startRange + 1;
1645 // Should only move cursor to a line.
1646 range.location = sr->lineNum + 1;
1650 [dict setObject:NSStringFromRange(range) forKey:@"selectionRange"];
1653 // 3. Extract Spotlight search text (if any)
1654 NSAppleEventDescriptor *spotlightdesc =
1655 [desc paramDescriptorForKeyword:keyAESearchText];
1657 [dict setObject:[spotlightdesc stringValue] forKey:@"searchText"];
1662 #ifdef MM_ENABLE_PLUGINS
1663 - (void)removePlugInMenu
1665 if ([plugInMenuItem menu])
1666 [[plugInMenuItem menu] removeItem:plugInMenuItem];
1669 - (void)addPlugInMenuToMenu:(NSMenu *)mainMenu
1671 NSMenu *windowsMenu = [mainMenu findWindowsMenu];
1673 if ([[plugInMenuItem submenu] numberOfItems] > 0) {
1674 int idx = windowsMenu ? [mainMenu indexOfItemWithSubmenu:windowsMenu]
1677 [mainMenu insertItem:plugInMenuItem atIndex:idx];
1679 [mainMenu addItem:plugInMenuItem];
1685 - (void)scheduleVimControllerPreloadAfterDelay:(NSTimeInterval)delay
1687 [self performSelector:@selector(preloadVimController:)
1692 - (void)cancelVimControllerPreloadRequests
1694 [NSObject cancelPreviousPerformRequestsWithTarget:self
1695 selector:@selector(preloadVimController:)
1699 - (void)preloadVimController:(id)sender
1701 // We only allow preloading of one Vim process at a time (to avoid hogging
1702 // CPU), so schedule another preload in a little while if necessary.
1703 if (-1 != preloadPid) {
1704 [self scheduleVimControllerPreloadAfterDelay:2];
1708 if ([cachedVimControllers count] >= [self maxPreloadCacheSize])
1711 preloadPid = [self launchVimProcessWithArguments:
1712 [NSArray arrayWithObject:@"--mmwaitforack"]];
1715 - (int)maxPreloadCacheSize
1717 // The maximum number of Vim processes to keep in the cache can be
1718 // controlled via the user default "MMPreloadCacheSize".
1719 int maxCacheSize = [[NSUserDefaults standardUserDefaults]
1720 integerForKey:MMPreloadCacheSizeKey];
1721 if (maxCacheSize < 0) maxCacheSize = 0;
1722 else if (maxCacheSize > 10) maxCacheSize = 10;
1724 return maxCacheSize;
1727 - (MMVimController *)takeVimControllerFromCache
1729 // NOTE: After calling this message the backend corresponding to the
1730 // returned vim controller must be sent an acknowledgeConnection message,
1731 // else the vim process will be stuck.
1733 // This method may return nil even though the cache might be non-empty; the
1734 // caller should handle this by starting a new Vim process.
1736 int i, count = [cachedVimControllers count];
1737 if (0 == count) return nil;
1739 // Locate the first Vim controller with up-to-date rc-files sourced.
1740 NSDate *rcDate = [self rcFilesModificationDate];
1741 for (i = 0; i < count; ++i) {
1742 MMVimController *vc = [cachedVimControllers objectAtIndex:i];
1743 NSDate *date = [vc creationDate];
1744 if ([date compare:rcDate] != NSOrderedAscending)
1749 // Clear out cache entries whose vimrc/gvimrc files were sourced before
1750 // the latest modification date for those files. This ensures that the
1751 // latest rc-files are always sourced for new windows.
1752 [self clearPreloadCacheWithCount:i];
1755 if ([cachedVimControllers count] == 0) {
1756 [self scheduleVimControllerPreloadAfterDelay:2.0];
1760 MMVimController *vc = [cachedVimControllers objectAtIndex:0];
1761 [vimControllers addObject:vc];
1762 [cachedVimControllers removeObjectAtIndex:0];
1763 [vc setIsPreloading:NO];
1765 // If the Vim process has finished loading then the window will displayed
1766 // now, otherwise it will be displayed when the OpenWindowMsgID message is
1768 [[vc windowController] showWindow];
1770 // Since we've taken one controller from the cache we take the opportunity
1771 // to preload another.
1772 [self scheduleVimControllerPreloadAfterDelay:1];
1777 - (void)clearPreloadCacheWithCount:(int)count
1779 // Remove the 'count' first entries in the preload cache. It is assumed
1780 // that objects are added/removed from the cache in a FIFO manner so that
1781 // this effectively clears the 'count' oldest entries.
1782 // If 'count' is negative, then the entire cache is cleared.
1784 if ([cachedVimControllers count] == 0 || count == 0)
1788 count = [cachedVimControllers count];
1790 // Make sure the preloaded Vim processes get killed or they'll just hang
1791 // around being useless until MacVim is terminated.
1792 NSEnumerator *e = [cachedVimControllers objectEnumerator];
1793 MMVimController *vc;
1795 while ((vc = [e nextObject]) && n-- > 0) {
1796 [[NSNotificationCenter defaultCenter] removeObserver:vc];
1797 [vc sendMessage:TerminateNowMsgID data:nil];
1799 // Since the preloaded processes were killed "prematurely" we have to
1800 // manually tell them to cleanup (it is not enough to simply release
1801 // them since deallocation and cleanup are separated).
1806 while (n-- > 0 && [cachedVimControllers count] > 0)
1807 [cachedVimControllers removeObjectAtIndex:0];
1809 // There is a small delay before the Vim process actually exits so wait a
1810 // little before trying to reap the child process. If the process still
1811 // hasn't exited after this wait it won't be reaped until the next time
1812 // reapChildProcesses: is called (but this should be harmless).
1813 [self performSelector:@selector(reapChildProcesses:)
1818 - (void)rebuildPreloadCache
1820 if ([self maxPreloadCacheSize] > 0) {
1821 [self clearPreloadCacheWithCount:-1];
1822 [self cancelVimControllerPreloadRequests];
1823 [self scheduleVimControllerPreloadAfterDelay:1.0];
1827 - (NSDate *)rcFilesModificationDate
1829 // Check modification dates for ~/.vimrc and ~/.gvimrc and return the
1830 // latest modification date. If ~/.vimrc does not exist, check ~/_vimrc
1831 // and similarly for gvimrc.
1832 // Returns distantPath if no rc files were found.
1834 NSDate *date = [NSDate distantPast];
1835 NSFileManager *fm = [NSFileManager defaultManager];
1837 NSString *path = [@"~/.vimrc" stringByExpandingTildeInPath];
1838 NSDictionary *attr = [fm fileAttributesAtPath:path traverseLink:YES];
1840 path = [@"~/_vimrc" stringByExpandingTildeInPath];
1841 attr = [fm fileAttributesAtPath:path traverseLink:YES];
1843 NSDate *modDate = [attr objectForKey:NSFileModificationDate];
1847 path = [@"~/.gvimrc" stringByExpandingTildeInPath];
1848 attr = [fm fileAttributesAtPath:path traverseLink:YES];
1850 path = [@"~/_gvimrc" stringByExpandingTildeInPath];
1851 attr = [fm fileAttributesAtPath:path traverseLink:YES];
1853 modDate = [attr objectForKey:NSFileModificationDate];
1855 date = [date laterDate:modDate];
1860 - (BOOL)openVimControllerWithArguments:(NSDictionary *)arguments
1862 MMVimController *vc = [self findUnusedEditor];
1864 // Open files in an already open window.
1865 [[[vc windowController] window] makeKeyAndOrderFront:self];
1866 [vc passArguments:arguments];
1867 } else if ((vc = [self takeVimControllerFromCache])) {
1868 // Open files in a new window using a cached vim controller. This
1869 // requires virtually no loading time so the new window will pop up
1871 [vc passArguments:arguments];
1872 [[vc backendProxy] acknowledgeConnection];
1874 // Open files in a launching Vim process or start a new process. This
1875 // may take 1-2 seconds so there will be a visible delay before the
1876 // window appears on screen.
1877 int pid = [self findLaunchingProcessWithoutArguments];
1879 pid = [self launchVimProcessWithArguments:nil];
1884 // TODO: If the Vim process fails to start, or if it changes PID,
1885 // then the memory allocated for these parameters will leak.
1886 // Ensure that this cannot happen or somehow detect it.
1888 if ([arguments count] > 0)
1889 [pidArguments setObject:arguments
1890 forKey:[NSNumber numberWithInt:pid]];
1896 - (void)activateWhenNextWindowOpens
1898 shouldActivateWhenNextWindowOpens = YES;
1901 - (void)startWatchingVimDir
1903 //NSLog(@"%s", _cmd);
1904 #if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4)
1907 if (NULL == FSEventStreamStart)
1908 return; // FSEvent functions are weakly linked
1910 NSString *path = [@"~/.vim" stringByExpandingTildeInPath];
1911 NSArray *pathsToWatch = [NSArray arrayWithObject:path];
1913 fsEventStream = FSEventStreamCreate(NULL, &fsEventCallback, NULL,
1914 (CFArrayRef)pathsToWatch, kFSEventStreamEventIdSinceNow,
1915 MMEventStreamLatency, kFSEventStreamCreateFlagNone);
1917 FSEventStreamScheduleWithRunLoop(fsEventStream,
1918 [[NSRunLoop currentRunLoop] getCFRunLoop],
1919 kCFRunLoopDefaultMode);
1921 FSEventStreamStart(fsEventStream);
1922 //NSLog(@"Started FS event stream");
1926 - (void)stopWatchingVimDir
1928 //NSLog(@"%s", _cmd);
1929 #if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4)
1930 if (NULL == FSEventStreamStop)
1931 return; // FSEvent functions are weakly linked
1933 if (fsEventStream) {
1934 FSEventStreamStop(fsEventStream);
1935 FSEventStreamInvalidate(fsEventStream);
1936 FSEventStreamRelease(fsEventStream);
1937 fsEventStream = NULL;
1938 //NSLog(@"Stopped FS event stream");
1944 - (void)handleFSEvent
1946 //NSLog(@"%s", _cmd);
1947 [self clearPreloadCacheWithCount:-1];
1949 // Several FS events may arrive in quick succession so make sure to cancel
1950 // any previous preload requests before making a new one.
1951 [self cancelVimControllerPreloadRequests];
1952 [self scheduleVimControllerPreloadAfterDelay:0.5];
1955 - (void)loadDefaultFont
1957 // It is possible to set a user default to avoid loading the default font
1958 // (this cuts down on startup time).
1959 if (![[NSUserDefaults standardUserDefaults] boolForKey:MMLoadDefaultFontKey]
1960 || fontContainerRef)
1963 // Load all fonts in the Resouces folder of the app bundle.
1964 NSString *fontsFolder = [[NSBundle mainBundle] resourcePath];
1966 NSURL *fontsURL = [NSURL fileURLWithPath:fontsFolder];
1969 CFURLGetFSRef((CFURLRef)fontsURL, &fsRef);
1971 #if (MAC_OS_X_VERSION_MAX_ALLOWED > MAC_OS_X_VERSION_10_4)
1972 // This is the font activation API for OS X 10.5. Only compile
1973 // this code if we're building on OS X 10.5 or later.
1974 if (NULL != ATSFontActivateFromFileReference) { // Weakly linked
1975 ATSFontActivateFromFileReference(&fsRef, kATSFontContextLocal,
1976 kATSFontFormatUnspecified,
1977 NULL, kATSOptionFlagsDefault,
1981 #if (MAC_OS_X_VERSION_MIN_REQUIRED <= MAC_OS_X_VERSION_10_4)
1982 // The following font activation API was deprecated in OS X 10.5.
1983 // Don't compile this code unless we're targeting OS X 10.4.
1985 if (fontContainerRef == 0 &&
1986 FSGetCatalogInfo(&fsRef, kFSCatInfoNone, NULL, NULL,
1987 &fsSpec, NULL) == noErr) {
1988 ATSFontActivateFromFileSpecification(&fsSpec,
1989 kATSFontContextLocal, kATSFontFormatUnspecified, NULL,
1990 kATSOptionFlagsDefault, &fontContainerRef);
1996 if (!fontContainerRef)
1997 NSLog(@"WARNING: Failed to activate the default font (the app bundle "
1998 "may be incomplete)");
2001 - (int)executeInLoginShell:(NSString *)path arguments:(NSArray *)args
2003 // Start a login shell and execute the command 'path' with arguments 'args'
2004 // in the shell. This ensures that user environment variables are set even
2005 // when MacVim was started from the Finder.
2008 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
2010 // Determine which shell to use to execute the command. The user
2011 // may decide which shell to use by setting a user default or the
2012 // $SHELL environment variable.
2013 NSString *shell = [ud stringForKey:MMLoginShellCommandKey];
2014 if (!shell || [shell length] == 0)
2015 shell = [[[NSProcessInfo processInfo] environment]
2016 objectForKey:@"SHELL"];
2018 shell = @"/bin/bash";
2020 //NSLog(@"shell = %@", shell);
2022 // Bash needs the '-l' flag to launch a login shell. The user may add
2023 // flags by setting a user default.
2024 NSString *shellArgument = [ud stringForKey:MMLoginShellArgumentKey];
2025 if (!shellArgument || [shellArgument length] == 0) {
2026 if ([[shell lastPathComponent] isEqual:@"bash"])
2027 shellArgument = @"-l";
2029 shellArgument = nil;
2032 //NSLog(@"shellArgument = %@", shellArgument);
2034 // Build input string to pipe to the login shell.
2035 NSMutableString *input = [NSMutableString stringWithFormat:
2036 @"exec \"%@\"", path];
2038 // Append all arguments, making sure they are properly quoted, even
2039 // when they contain single quotes.
2040 NSEnumerator *e = [args objectEnumerator];
2043 while ((obj = [e nextObject])) {
2044 NSMutableString *arg = [NSMutableString stringWithString:obj];
2045 [arg replaceOccurrencesOfString:@"'" withString:@"'\"'\"'"
2046 options:NSLiteralSearch
2047 range:NSMakeRange(0, [arg length])];
2048 [input appendFormat:@" '%@'", arg];
2052 // Build the argument vector used to start the login shell.
2053 NSString *shellArg0 = [NSString stringWithFormat:@"-%@",
2054 [shell lastPathComponent]];
2055 char *shellArgv[3] = { (char *)[shellArg0 UTF8String], NULL, NULL };
2057 shellArgv[1] = (char *)[shellArgument UTF8String];
2059 // Get the C string representation of the shell path before the fork since
2060 // we must not call Foundation functions after a fork.
2061 const char *shellPath = [shell fileSystemRepresentation];
2063 // Fork and execute the process.
2065 if (pipe(ds)) return -1;
2070 } else if (pid == 0) {
2073 if (close(ds[1]) == -1) exit(255);
2074 if (dup2(ds[0], 0) == -1) exit(255);
2076 // Without the following call warning messages like this appear on the
2078 // com.apple.launchd[69] : Stray process with PGID equal to this
2079 // dead job: PID 1589 PPID 1 Vim
2082 execv(shellPath, shellArgv);
2084 // Never reached unless execv fails
2088 if (close(ds[0]) == -1) return -1;
2090 // Send input to execute to the child process
2091 [input appendString:@"\n"];
2092 int bytes = [input lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
2094 if (write(ds[1], [input UTF8String], bytes) != bytes) return -1;
2095 if (close(ds[1]) == -1) return -1;
2097 ++numChildProcesses;
2098 //NSLog(@"new process pid=%d (count=%d)", pid, numChildProcesses);
2104 - (void)reapChildProcesses:(id)sender
2106 // NOTE: numChildProcesses (currently) only counts the number of Vim
2107 // processes that have been started with executeInLoginShell::. If other
2108 // processes are spawned this code may need to be adjusted (or
2109 // numChildProcesses needs to be incremented when such a process is
2111 while (numChildProcesses > 0) {
2113 int pid = waitpid(-1, &status, WNOHANG);
2117 //NSLog(@"WAIT for pid=%d complete", pid);
2118 --numChildProcesses;
2122 - (void)processInputQueues:(id)sender
2124 // NOTE: Because we use distributed objects it is quite possible for this
2125 // function to be re-entered. This can cause all sorts of unexpected
2126 // problems so we guard against it here so that the rest of the code does
2127 // not need to worry about it.
2129 // The processing flag is > 0 if this function is already on the call
2130 // stack; < 0 if this function was also re-entered.
2131 if (processingFlag != 0) {
2132 NSLog(@"[%s] BUSY!", _cmd);
2133 processingFlag = -1;
2137 // NOTE: Be _very_ careful that no exceptions can be raised between here
2138 // and the point at which 'processingFlag' is reset. Otherwise the above
2139 // test could end up always failing and no input queues would ever be
2143 // NOTE: New input may arrive while we're busy processing; we deal with
2144 // this by putting the current queue aside and creating a new input queue
2145 // for future input.
2146 NSDictionary *queues = inputQueues;
2147 inputQueues = [NSMutableDictionary new];
2149 // Pass each input queue on to the vim controller with matching
2150 // identifier (and note that it could be cached).
2151 NSEnumerator *e = [queues keyEnumerator];
2153 while ((key = [e nextObject])) {
2154 unsigned ukey = [key unsignedIntValue];
2155 int i = 0, count = [vimControllers count];
2156 for (i = 0; i < count; ++i) {
2157 MMVimController *vc = [vimControllers objectAtIndex:i];
2158 if (ukey == [vc identifier]) {
2159 [vc processInputQueue:[queues objectForKey:key]]; // !exceptions
2164 if (i < count) continue;
2166 count = [cachedVimControllers count];
2167 for (i = 0; i < count; ++i) {
2168 MMVimController *vc = [cachedVimControllers objectAtIndex:i];
2169 if (ukey == [vc identifier]) {
2170 [vc processInputQueue:[queues objectForKey:key]]; // !exceptions
2176 NSLog(@"[%s] WARNING: No Vim controller for identifier=%d",
2182 // If new input arrived while we were processing it would have been
2183 // blocked so we have to schedule it to be processed again.
2184 if (processingFlag < 0)
2185 [self performSelector:@selector(processInputQueues:)
2188 inModes:[NSArray arrayWithObjects:NSDefaultRunLoopMode,
2189 NSEventTrackingRunLoopMode, nil]];
2194 - (void)addVimController:(MMVimController *)vc
2197 NSNumber *pidKey = [NSNumber numberWithInt:pid];
2199 if (preloadPid == pid) {
2200 // This controller was preloaded, so add it to the cache and
2201 // schedule another vim process to be preloaded.
2203 [vc setIsPreloading:YES];
2204 [cachedVimControllers addObject:vc];
2205 [self scheduleVimControllerPreloadAfterDelay:1];
2207 [vimControllers addObject:vc];
2209 id args = [pidArguments objectForKey:pidKey];
2210 if (args && [NSNull null] != args)
2211 [vc passArguments:args];
2213 // HACK! MacVim does not get activated if it is launched from the
2214 // terminal, so we forcibly activate here unless it is an untitled
2215 // window opening. Untitled windows are treated differently, else
2216 // MacVim would steal the focus if another app was activated while the
2217 // untitled window was loading.
2218 if (!args || args != [NSNull null])
2219 [self activateWhenNextWindowOpens];
2222 [pidArguments removeObjectForKey:pidKey];
2226 @end // MMAppController (Private)