1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
3 * VIM - Vi IMproved by Bram Moolenaar
4 * MacVim GUI port by Bjorn Winckler
6 * Do ":help uganda" in Vim to read copying and usage conditions.
7 * Do ":help credits" in Vim to see a list of people who contributed.
8 * See README.txt for an overview of the Vim source code.
13 * MMAppController is the delegate of NSApp and as such handles file open
14 * requests, application termination, etc. It sets up a named NSConnection on
15 * which it listens to incoming connections from Vim processes. It also
16 * coordinates all MMVimControllers.
18 * A new Vim process is started by calling launchVimProcessWithArguments:.
19 * When the Vim process is initialized it notifies the app controller by
20 * sending a connectBackend:pid: message. At this point a new MMVimController
21 * is allocated. Afterwards, the Vim process communicates directly with its
24 * A Vim process started from the command line connects directly by sending the
25 * connectBackend:pid: message (launchVimProcessWithArguments: is never called
29 #import "MMAppController.h"
30 #import "MMVimController.h"
31 #import "MMWindowController.h"
32 #import "MMPreferenceController.h"
35 #define MM_HANDLE_XCODE_MOD_EVENT 0
39 // Default timeout intervals on all connections.
40 static NSTimeInterval MMRequestTimeout = 5;
41 static NSTimeInterval MMReplyTimeout = 5;
44 #pragma options align=mac68k
47 short unused1; // 0 (not used)
48 short lineNum; // line to select (< 0 to specify range)
49 long startRange; // start of selection range (if line < 0)
50 long endRange; // end of selection range (if line < 0)
51 long unused2; // 0 (not used)
52 long theDate; // modification date/time
54 #pragma options align=reset
57 @interface MMAppController (MMServices)
58 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
59 error:(NSString **)error;
60 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
61 error:(NSString **)error;
65 @interface MMAppController (Private)
66 - (MMVimController *)keyVimController;
67 - (MMVimController *)topmostVimController;
68 - (int)launchVimProcessWithArguments:(NSArray *)args;
69 - (NSArray *)filterFilesAndNotify:(NSArray *)files;
70 - (NSArray *)filterOpenFiles:(NSArray *)filenames
71 arguments:(NSDictionary *)args;
72 #if MM_HANDLE_XCODE_MOD_EVENT
73 - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
74 replyEvent:(NSAppleEventDescriptor *)reply;
76 - (int)findLaunchingProcessWithoutArguments;
77 - (MMVimController *)findUntitledWindow;
78 - (NSMutableDictionary *)extractArgumentsFromOdocEvent:
79 (NSAppleEventDescriptor *)desc;
80 - (void)passArguments:(NSDictionary *)args toVimController:(MMVimController*)vc;
83 @interface NSMenu (MMExtras)
84 - (void)recurseSetAutoenablesItems:(BOOL)on;
87 @interface NSNumber (MMExtras)
93 @implementation MMAppController
97 NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
98 [NSNumber numberWithBool:NO], MMNoWindowKey,
99 [NSNumber numberWithInt:64], MMTabMinWidthKey,
100 [NSNumber numberWithInt:6*64], MMTabMaxWidthKey,
101 [NSNumber numberWithInt:132], MMTabOptimumWidthKey,
102 [NSNumber numberWithInt:2], MMTextInsetLeftKey,
103 [NSNumber numberWithInt:1], MMTextInsetRightKey,
104 [NSNumber numberWithInt:1], MMTextInsetTopKey,
105 [NSNumber numberWithInt:1], MMTextInsetBottomKey,
106 [NSNumber numberWithBool:NO], MMTerminateAfterLastWindowClosedKey,
107 @"MMTypesetter", MMTypesetterKey,
108 [NSNumber numberWithFloat:1], MMCellWidthMultiplierKey,
109 [NSNumber numberWithFloat:-1], MMBaselineOffsetKey,
110 [NSNumber numberWithBool:YES], MMTranslateCtrlClickKey,
111 [NSNumber numberWithBool:NO], MMOpenFilesInTabsKey,
112 [NSNumber numberWithBool:NO], MMNoFontSubstitutionKey,
113 [NSNumber numberWithBool:NO], MMLoginShellKey,
114 [NSNumber numberWithBool:NO], MMAtsuiRendererKey,
115 [NSNumber numberWithInt:MMUntitledWindowAlways],
117 [NSNumber numberWithBool:NO], MMTexturedWindowKey,
118 [NSNumber numberWithBool:NO], MMZoomBothKey,
121 [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
123 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
124 [NSApp registerServicesMenuSendTypes:types returnTypes:types];
129 if ((self = [super init])) {
130 fontContainerRef = loadFonts();
132 vimControllers = [NSMutableArray new];
133 pidArguments = [NSMutableDictionary new];
135 // NOTE! If the name of the connection changes here it must also be
136 // updated in MMBackend.m.
137 NSConnection *connection = [NSConnection defaultConnection];
138 NSString *name = [NSString stringWithFormat:@"%@-connection",
139 [[NSBundle mainBundle] bundleIdentifier]];
140 //NSLog(@"Registering connection with name '%@'", name);
141 if ([connection registerName:name]) {
142 [connection setRequestTimeout:MMRequestTimeout];
143 [connection setReplyTimeout:MMReplyTimeout];
144 [connection setRootObject:self];
146 // NOTE: When the user is resizing the window the AppKit puts the
147 // run loop in event tracking mode. Unless the connection listens
148 // to request in this mode, live resizing won't work.
149 [connection addRequestMode:NSEventTrackingRunLoopMode];
151 NSLog(@"WARNING: Failed to register connection with name '%@'",
161 //NSLog(@"MMAppController dealloc");
163 [pidArguments release]; pidArguments = nil;
164 [vimControllers release]; vimControllers = nil;
165 [openSelectionString release]; openSelectionString = nil;
170 #if MM_HANDLE_XCODE_MOD_EVENT
171 - (void)applicationWillFinishLaunching:(NSNotification *)notification
173 [[NSAppleEventManager sharedAppleEventManager]
175 andSelector:@selector(handleXcodeModEvent:replyEvent:)
181 - (void)applicationDidFinishLaunching:(NSNotification *)notification
183 [NSApp setServicesProvider:self];
186 - (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
188 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
189 NSAppleEventManager *aem = [NSAppleEventManager sharedAppleEventManager];
190 NSAppleEventDescriptor *desc = [aem currentAppleEvent];
192 // The user default MMUntitledWindow can be set to control whether an
193 // untitled window should open on 'Open' and 'Reopen' events.
194 int untitledWindowFlag = [ud integerForKey:MMUntitledWindowKey];
195 if ([desc eventID] == kAEOpenApplication
196 && (untitledWindowFlag & MMUntitledWindowOnOpen) == 0)
198 else if ([desc eventID] == kAEReopenApplication
199 && (untitledWindowFlag & MMUntitledWindowOnReopen) == 0)
202 // When a process is started from the command line, the 'Open' event will
203 // contain a parameter to surpress the opening of an untitled window.
204 desc = [desc paramDescriptorForKeyword:keyAEPropData];
205 desc = [desc paramDescriptorForKeyword:keyMMUntitledWindow];
206 if (desc && ![desc booleanValue])
209 // Never open an untitled window if there is at least one open window or if
210 // there are processes that are currently launching.
211 if ([vimControllers count] > 0 || [pidArguments count] > 0)
214 // NOTE! This way it possible to start the app with the command-line
215 // argument '-nowindow yes' and no window will be opened by default.
216 return ![ud boolForKey:MMNoWindowKey];
219 - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
221 [self newWindow:self];
225 - (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
227 // Opening files works like this:
228 // a) extract ODB/Xcode/Spotlight parameters from the current Apple event
229 // b) filter out any already open files (see filterOpenFiles::)
230 // c) open any remaining files
232 // A file is opened in an untitled window if there is one (it may be
233 // currently launching, or it may already be visible), otherwise a new
236 // Each launching Vim process has a dictionary of arguments that are passed
237 // to the process when in checks in (via connectBackend:pid:). The
238 // arguments for each launching process can be looked up by its PID (in the
239 // pidArguments dictionary).
241 NSMutableDictionary *arguments = [self extractArgumentsFromOdocEvent:
242 [[NSAppleEventManager sharedAppleEventManager] currentAppleEvent]];
244 // Filter out files that are already open
245 filenames = [self filterOpenFiles:filenames arguments:arguments];
247 // Open any files that remain
248 if ([filenames count]) {
250 BOOL openInTabs = [[NSUserDefaults standardUserDefaults]
251 boolForKey:MMOpenFilesInTabsKey];
253 [arguments setObject:filenames forKey:@"filenames"];
254 [arguments setObject:[NSNumber numberWithBool:YES] forKey:@"openFiles"];
256 if ((openInTabs && (vc = [self topmostVimController]))
257 || (vc = [self findUntitledWindow])) {
258 // Open files in an already open window.
259 [[[vc windowController] window] makeKeyAndOrderFront:self];
260 [self passArguments:arguments toVimController:vc];
262 // Open files in a launching Vim process or start a new process.
263 int pid = [self findLaunchingProcessWithoutArguments];
265 // Pass the filenames to the process straight away.
267 // TODO: It would be nicer if all arguments were passed to the
268 // Vim process in connectBackend::, but if we don't pass the
269 // filename arguments here, the window 'flashes' once when it
270 // opens. This is due to the 'welcome' screen first being
271 // displayed, then quickly thereafter the files are opened.
272 NSArray *fileArgs = [NSArray arrayWithObject:@"-p"];
273 fileArgs = [fileArgs arrayByAddingObjectsFromArray:filenames];
275 pid = [self launchVimProcessWithArguments:fileArgs];
277 // Make sure these files aren't opened again when
278 // connectBackend:pid: is called.
279 [arguments setObject:[NSNumber numberWithBool:NO]
280 forKey:@"openFiles"];
283 // TODO: If the Vim process fails to start, or if it changes PID,
284 // then the memory allocated for these parameters will leak.
285 // Ensure that this cannot happen or somehow detect it.
287 if ([arguments count] > 0)
288 [pidArguments setObject:arguments
289 forKey:[NSNumber numberWithInt:pid]];
293 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
294 // NSApplicationDelegateReplySuccess = 0,
295 // NSApplicationDelegateReplyCancel = 1,
296 // NSApplicationDelegateReplyFailure = 2
299 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
301 return [[NSUserDefaults standardUserDefaults]
302 boolForKey:MMTerminateAfterLastWindowClosedKey];
305 - (NSApplicationTerminateReply)applicationShouldTerminate:
306 (NSApplication *)sender
308 // TODO: Follow Apple's guidelines for 'Graceful Application Termination'
309 // (in particular, allow user to review changes and save).
310 int reply = NSTerminateNow;
311 BOOL modifiedBuffers = NO;
313 // Go through windows, checking for modified buffers. (Each Vim process
314 // tells MacVim when any buffer has been modified and MacVim sets the
315 // 'documentEdited' flag of the window correspondingly.)
316 NSEnumerator *e = [[NSApp windows] objectEnumerator];
318 while ((window = [e nextObject])) {
319 if ([window isDocumentEdited]) {
320 modifiedBuffers = YES;
325 if (modifiedBuffers) {
326 NSAlert *alert = [[NSAlert alloc] init];
327 [alert setAlertStyle:NSWarningAlertStyle];
328 [alert addButtonWithTitle:@"Quit"];
329 [alert addButtonWithTitle:@"Cancel"];
330 [alert setMessageText:@"Quit without saving?"];
331 [alert setInformativeText:@"There are modified buffers, "
332 "if you quit now all changes will be lost. Quit anyway?"];
334 if ([alert runModal] != NSAlertFirstButtonReturn)
335 reply = NSTerminateCancel;
339 // No unmodified buffers, but give a warning if there are multiple
340 // windows and/or tabs open.
341 int numWindows = [vimControllers count];
344 // Count the number of open tabs
345 e = [vimControllers objectEnumerator];
347 while ((vc = [e nextObject])) {
348 NSString *eval = [vc evaluateVimExpression:@"tabpagenr('$')"];
350 int count = [eval intValue];
351 if (count > 0 && count < INT_MAX)
356 if (numWindows > 1 || numTabs > 1) {
357 NSAlert *alert = [[NSAlert alloc] init];
358 [alert setAlertStyle:NSWarningAlertStyle];
359 [alert addButtonWithTitle:@"Quit"];
360 [alert addButtonWithTitle:@"Cancel"];
361 [alert setMessageText:@"Are you sure you want to quit MacVim?"];
363 NSString *info = nil;
364 if (numWindows > 1) {
365 if (numTabs > numWindows)
366 info = [NSString stringWithFormat:@"There are %d windows "
367 "open in MacVim, with a total of %d tabs. Do you want "
368 "to quit anyway?", numWindows, numTabs];
370 info = [NSString stringWithFormat:@"There are %d windows "
371 "open in MacVim. Do you want to quit anyway?",
375 info = [NSString stringWithFormat:@"There are %d tabs open "
376 "in MacVim. Do you want to quit anyway?", numTabs];
379 [alert setInformativeText:info];
381 if ([alert runModal] != NSAlertFirstButtonReturn)
382 reply = NSTerminateCancel;
389 // Tell all Vim processes to terminate now (otherwise they'll leave swap
391 if (NSTerminateNow == reply) {
392 e = [vimControllers objectEnumerator];
394 while ((vc = [e nextObject]))
395 [vc sendMessage:TerminateNowMsgID data:nil];
401 - (void)applicationWillTerminate:(NSNotification *)notification
403 #if MM_HANDLE_XCODE_MOD_EVENT
404 [[NSAppleEventManager sharedAppleEventManager]
405 removeEventHandlerForEventClass:'KAHL'
409 // This will invalidate all connections (since they were spawned from the
410 // default connection).
411 [[NSConnection defaultConnection] invalidate];
413 // Send a SIGINT to all running Vim processes, so that they are sure to
414 // receive the connectionDidDie: notification (a process has to be checking
415 // the run-loop for this to happen).
416 unsigned i, count = [vimControllers count];
417 for (i = 0; i < count; ++i) {
418 MMVimController *controller = [vimControllers objectAtIndex:i];
419 int pid = [controller pid];
424 if (fontContainerRef) {
425 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
426 fontContainerRef = 0;
429 [NSApp setDelegate:nil];
432 - (void)removeVimController:(id)controller
434 //NSLog(@"%s%@", _cmd, controller);
436 [[controller windowController] close];
438 [vimControllers removeObject:controller];
440 if (![vimControllers count]) {
441 // Turn on autoenabling of menus (because no Vim is open to handle it),
442 // but do not touch the MacVim menu. Note that the menus must be
443 // enabled first otherwise autoenabling does not work.
444 NSMenu *mainMenu = [NSApp mainMenu];
445 int i, count = [mainMenu numberOfItems];
446 for (i = 1; i < count; ++i) {
447 NSMenuItem *item = [mainMenu itemAtIndex:i];
448 [item setEnabled:YES];
449 [[item submenu] recurseSetAutoenablesItems:YES];
454 - (void)windowControllerWillOpen:(MMWindowController *)windowController
456 NSPoint topLeft = NSZeroPoint;
457 NSWindow *keyWin = [NSApp keyWindow];
458 NSWindow *win = [windowController window];
462 // If there is a key window, cascade from it, otherwise use the autosaved
463 // window position (if any).
465 NSRect frame = [keyWin frame];
466 topLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
468 NSString *topLeftString = [[NSUserDefaults standardUserDefaults]
469 stringForKey:MMTopLeftPointKey];
471 topLeft = NSPointFromString(topLeftString);
474 if (!NSEqualPoints(topLeft, NSZeroPoint)) {
476 topLeft = [win cascadeTopLeftFromPoint:topLeft];
478 [win setFrameTopLeftPoint:topLeft];
481 if (openSelectionString) {
482 // TODO: Pass this as a parameter instead! Get rid of
483 // 'openSelectionString' etc.
485 // There is some text to paste into this window as a result of the
486 // services menu "Open selection ..." being used.
487 [[windowController vimController] dropString:openSelectionString];
488 [openSelectionString release];
489 openSelectionString = nil;
493 - (IBAction)newWindow:(id)sender
495 [self launchVimProcessWithArguments:nil];
498 - (IBAction)fileOpen:(id)sender
500 NSOpenPanel *panel = [NSOpenPanel openPanel];
501 [panel setAllowsMultipleSelection:YES];
503 int result = [panel runModalForTypes:nil];
504 if (NSOKButton == result)
505 [self application:NSApp openFiles:[panel filenames]];
508 - (IBAction)selectNextWindow:(id)sender
510 unsigned i, count = [vimControllers count];
513 NSWindow *keyWindow = [NSApp keyWindow];
514 for (i = 0; i < count; ++i) {
515 MMVimController *vc = [vimControllers objectAtIndex:i];
516 if ([[[vc windowController] window] isEqual:keyWindow])
523 MMVimController *vc = [vimControllers objectAtIndex:i];
524 [[vc windowController] showWindow:self];
528 - (IBAction)selectPreviousWindow:(id)sender
530 unsigned i, count = [vimControllers count];
533 NSWindow *keyWindow = [NSApp keyWindow];
534 for (i = 0; i < count; ++i) {
535 MMVimController *vc = [vimControllers objectAtIndex:i];
536 if ([[[vc windowController] window] isEqual:keyWindow])
546 MMVimController *vc = [vimControllers objectAtIndex:i];
547 [[vc windowController] showWindow:self];
551 - (IBAction)fontSizeUp:(id)sender
553 [[NSFontManager sharedFontManager] modifyFont:
554 [NSNumber numberWithInt:NSSizeUpFontAction]];
557 - (IBAction)fontSizeDown:(id)sender
559 [[NSFontManager sharedFontManager] modifyFont:
560 [NSNumber numberWithInt:NSSizeDownFontAction]];
563 - (IBAction)orderFrontPreferencePanel:(id)sender
565 [[MMPreferenceController sharedPreferenceController] showWindow:self];
568 - (byref id <MMFrontendProtocol>)
569 connectBackend:(byref in id <MMBackendProtocol>)backend
572 //NSLog(@"Connect backend (pid=%d)", pid);
573 NSNumber *pidKey = [NSNumber numberWithInt:pid];
574 MMVimController *vc = nil;
577 [(NSDistantObject*)backend
578 setProtocolForProxy:@protocol(MMBackendProtocol)];
580 vc = [[[MMVimController alloc]
581 initWithBackend:backend pid:pid] autorelease];
583 if (![vimControllers count]) {
584 // The first window autosaves its position. (The autosaving
585 // features of Cocoa are not used because we need more control over
586 // what is autosaved and when it is restored.)
587 [[vc windowController] setWindowAutosaveKey:MMTopLeftPointKey];
590 [vimControllers addObject:vc];
592 id args = [pidArguments objectForKey:pidKey];
593 if (args && [NSNull null] != args)
594 [self passArguments:args toVimController:vc];
596 // HACK! MacVim does not get activated if it is launched from the
597 // terminal, so we forcibly activate here unless it is an untitled
598 // window opening. Untitled windows are treated differently, else
599 // MacVim would steal the focus if another app was activated while the
600 // untitled window was loading.
601 if (!args || args != [NSNull null])
602 [NSApp activateIgnoringOtherApps:YES];
605 [pidArguments removeObjectForKey:pidKey];
610 @catch (NSException *e) {
611 NSLog(@"Exception caught in %s: \"%@\"", _cmd, e);
614 [vimControllers removeObject:vc];
616 [pidArguments removeObjectForKey:pidKey];
622 - (NSArray *)serverList
624 NSMutableArray *array = [NSMutableArray array];
626 unsigned i, count = [vimControllers count];
627 for (i = 0; i < count; ++i) {
628 MMVimController *controller = [vimControllers objectAtIndex:i];
629 if ([controller serverName])
630 [array addObject:[controller serverName]];
636 @end // MMAppController
641 @implementation MMAppController (MMServices)
643 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
644 error:(NSString **)error
646 if (![[pboard types] containsObject:NSStringPboardType]) {
647 NSLog(@"WARNING: Pasteboard contains no object of type "
648 "NSStringPboardType");
652 MMVimController *vc = [self topmostVimController];
654 // Open a new tab first, since dropString: does not do this.
655 [vc sendMessage:AddNewTabMsgID data:nil];
656 [vc dropString:[pboard stringForType:NSStringPboardType]];
658 // NOTE: There is no window to paste the selection into, so save the
659 // text, open a new window, and paste the text when the next window
660 // opens. (If this is called several times in a row, then all but the
661 // last call might be ignored.)
662 if (openSelectionString) [openSelectionString release];
663 openSelectionString = [[pboard stringForType:NSStringPboardType] copy];
665 [self newWindow:self];
669 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
670 error:(NSString **)error
672 if (![[pboard types] containsObject:NSStringPboardType]) {
673 NSLog(@"WARNING: Pasteboard contains no object of type "
674 "NSStringPboardType");
678 // TODO: Parse multiple filenames and create array with names.
679 NSString *string = [pboard stringForType:NSStringPboardType];
680 string = [string stringByTrimmingCharactersInSet:
681 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
682 string = [string stringByStandardizingPath];
684 NSArray *filenames = [self filterFilesAndNotify:
685 [NSArray arrayWithObject:string]];
686 if ([filenames count] > 0) {
687 MMVimController *vc = nil;
688 if (userData && [userData isEqual:@"Tab"])
689 vc = [self topmostVimController];
692 [vc dropFiles:filenames forceOpen:YES];
694 [self application:NSApp openFiles:filenames];
699 @end // MMAppController (MMServices)
704 @implementation MMAppController (Private)
706 - (MMVimController *)keyVimController
708 NSWindow *keyWindow = [NSApp keyWindow];
710 unsigned i, count = [vimControllers count];
711 for (i = 0; i < count; ++i) {
712 MMVimController *vc = [vimControllers objectAtIndex:i];
713 if ([[[vc windowController] window] isEqual:keyWindow])
721 - (MMVimController *)topmostVimController
723 NSArray *windows = [NSApp orderedWindows];
724 if ([windows count] > 0) {
725 NSWindow *window = [windows objectAtIndex:0];
726 unsigned i, count = [vimControllers count];
727 for (i = 0; i < count; ++i) {
728 MMVimController *vc = [vimControllers objectAtIndex:i];
729 if ([[[vc windowController] window] isEqual:window])
737 - (int)launchVimProcessWithArguments:(NSArray *)args
739 NSString *taskPath = nil;
740 NSArray *taskArgs = nil;
741 NSString *path = [[NSBundle mainBundle] pathForAuxiliaryExecutable:@"Vim"];
744 NSLog(@"ERROR: Vim executable could not be found inside app bundle!");
748 if ([[NSUserDefaults standardUserDefaults] boolForKey:MMLoginShellKey]) {
749 // Run process with a login shell
750 // $SHELL -l -c "exec Vim -g -f args"
751 // (-g for GUI, -f for foreground, i.e. don't fork)
753 NSMutableString *execArg = [NSMutableString
754 stringWithFormat:@"exec \"%@\" -g -f", path];
756 // Append all arguments while making sure that arguments containing
757 // spaces are enclosed in quotes.
758 NSCharacterSet *space = [NSCharacterSet whitespaceCharacterSet];
759 unsigned i, count = [args count];
761 for (i = 0; i < count; ++i) {
762 NSString *arg = [args objectAtIndex:i];
763 if (NSNotFound != [arg rangeOfCharacterFromSet:space].location)
764 [execArg appendFormat:@" \"%@\"", arg];
766 [execArg appendFormat:@" %@", arg];
770 // Launch the process with a login shell so that users environment
771 // settings get sourced. This does not always happen when MacVim is
773 taskArgs = [NSArray arrayWithObjects:@"-l", @"-c", execArg, nil];
774 taskPath = [[[NSProcessInfo processInfo] environment]
775 objectForKey:@"SHELL"];
777 taskPath = @"/bin/sh";
779 // Run process directly:
781 // (-g for GUI, -f for foreground, i.e. don't fork)
783 taskArgs = [NSArray arrayWithObjects:@"-g", @"-f", nil];
785 taskArgs = [taskArgs arrayByAddingObjectsFromArray:args];
788 NSTask *task =[NSTask launchedTaskWithLaunchPath:taskPath
790 //NSLog(@"launch %@ with args=%@ (pid=%d)", taskPath, taskArgs,
791 // [task processIdentifier]);
793 int pid = [task processIdentifier];
795 // If the process has no arguments, then add a null argument to the
796 // pidArguments dictionary. This is later used to detect that a process
797 // without arguments is being launched.
799 [pidArguments setObject:[NSNull null]
800 forKey:[NSNumber numberWithInt:pid]];
805 - (NSArray *)filterFilesAndNotify:(NSArray *)filenames
807 // Go trough 'filenames' array and make sure each file exists. Present
808 // warning dialog if some file was missing.
810 NSString *firstMissingFile = nil;
811 NSMutableArray *files = [NSMutableArray array];
812 unsigned i, count = [filenames count];
814 for (i = 0; i < count; ++i) {
815 NSString *name = [filenames objectAtIndex:i];
816 if ([[NSFileManager defaultManager] fileExistsAtPath:name]) {
817 [files addObject:name];
818 } else if (!firstMissingFile) {
819 firstMissingFile = name;
823 if (firstMissingFile) {
824 NSAlert *alert = [[NSAlert alloc] init];
825 [alert addButtonWithTitle:@"OK"];
828 if ([files count] >= count-1) {
829 [alert setMessageText:@"File not found"];
830 text = [NSString stringWithFormat:@"Could not open file with "
831 "name %@.", firstMissingFile];
833 [alert setMessageText:@"Multiple files not found"];
834 text = [NSString stringWithFormat:@"Could not open file with "
835 "name %@, and %d other files.", firstMissingFile,
836 count-[files count]-1];
839 [alert setInformativeText:text];
840 [alert setAlertStyle:NSWarningAlertStyle];
845 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
851 - (NSArray *)filterOpenFiles:(NSArray *)filenames
852 arguments:(NSDictionary *)args
854 // Check if any of the files in the 'filenames' array are open in any Vim
855 // process. Remove the files that are open from the 'filenames' array and
856 // return it. If all files were filtered out, then raise the first file in
857 // the Vim process it is open. Files that are filtered are sent an odb
858 // open event in case theID is not zero.
860 NSMutableDictionary *localArgs =
861 [NSMutableDictionary dictionaryWithDictionary:args];
862 MMVimController *raiseController = nil;
863 NSString *raiseFile = nil;
864 NSMutableArray *files = [filenames mutableCopy];
865 NSString *expr = [NSString stringWithFormat:
866 @"map([\"%@\"],\"bufloaded(v:val)\")",
867 [files componentsJoinedByString:@"\",\""]];
868 unsigned i, count = [vimControllers count];
870 // Ensure that the files aren't opened when passing arguments.
871 [localArgs setObject:[NSNumber numberWithBool:NO] forKey:@"openFiles"];
873 for (i = 0; i < count && [files count]; ++i) {
874 MMVimController *controller = [vimControllers objectAtIndex:i];
876 // Query Vim for which files in the 'files' array are open.
877 NSString *eval = [controller evaluateVimExpression:expr];
880 NSIndexSet *idxSet = [NSIndexSet indexSetWithVimList:eval];
881 if ([idxSet count]) {
883 // Remember the file and which Vim that has it open so that
884 // we can raise it later on.
885 raiseController = controller;
886 raiseFile = [files objectAtIndex:[idxSet firstIndex]];
887 [[raiseFile retain] autorelease];
890 // Pass (ODB/Xcode/Spotlight) arguments to this process.
891 [localArgs setObject:[files objectsAtIndexes:idxSet]
892 forKey:@"filenames"];
893 [self passArguments:localArgs toVimController:controller];
895 // Remove all the files that were open in this Vim process and
896 // create a new expression to evaluate.
897 [files removeObjectsAtIndexes:idxSet];
898 expr = [NSString stringWithFormat:
899 @"map([\"%@\"],\"bufloaded(v:val)\")",
900 [files componentsJoinedByString:@"\",\""]];
904 if (![files count] && raiseFile) {
905 // Raise the window containing the first file that was already open,
906 // and make sure that the tab containing that file is selected. Only
907 // do this if there are no more files to open, otherwise sometimes the
908 // window with 'raiseFile' will be raised, other times it might be the
909 // window that will open with the files in the 'files' array.
910 raiseFile = [raiseFile stringByEscapingSpecialFilenameCharacters];
911 NSString *input = [NSString stringWithFormat:@"<C-\\><C-N>"
912 ":let oldswb=&swb|let &swb=\"useopen,usetab\"|"
913 "tab sb %@|let &swb=oldswb|unl oldswb|"
914 "cal foreground()|redr|f<CR>", raiseFile];
916 [raiseController addVimInput:input];
922 #if MM_HANDLE_XCODE_MOD_EVENT
923 - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
924 replyEvent:(NSAppleEventDescriptor *)reply
927 // Xcode sends this event to query MacVim which open files have been
929 NSLog(@"reply:%@", reply);
930 NSLog(@"event:%@", event);
932 NSEnumerator *e = [vimControllers objectEnumerator];
934 while ((vc = [e nextObject])) {
935 DescType type = [reply descriptorType];
936 unsigned len = [[type data] length];
937 NSMutableData *data = [NSMutableData data];
939 [data appendBytes:&type length:sizeof(DescType)];
940 [data appendBytes:&len length:sizeof(unsigned)];
941 [data appendBytes:[reply data] length:len];
943 [vc sendMessage:XcodeModMsgID data:data];
949 - (int)findLaunchingProcessWithoutArguments
951 NSArray *keys = [pidArguments allKeysForObject:[NSNull null]];
952 if ([keys count] > 0) {
953 //NSLog(@"found launching process without arguments");
954 return [[keys objectAtIndex:0] intValue];
960 - (MMVimController *)findUntitledWindow
962 NSEnumerator *e = [vimControllers objectEnumerator];
964 while ((vc = [e nextObject])) {
965 // TODO: This is a moronic test...should query the Vim process if there
966 // are any open buffers or something like that instead.
967 NSString *title = [[[vc windowController] window] title];
968 if ([title hasPrefix:@"[No Name] - VIM"]) {
969 //NSLog(@"found untitled window");
977 - (NSMutableDictionary *)extractArgumentsFromOdocEvent:
978 (NSAppleEventDescriptor *)desc
980 NSMutableDictionary *dict = [NSMutableDictionary dictionary];
982 // 1. Extract ODB parameters (if any)
983 NSAppleEventDescriptor *odbdesc = desc;
984 if (![odbdesc paramDescriptorForKeyword:keyFileSender]) {
985 // The ODB paramaters may hide inside the 'keyAEPropData' descriptor.
986 odbdesc = [odbdesc paramDescriptorForKeyword:keyAEPropData];
987 if (![odbdesc paramDescriptorForKeyword:keyFileSender])
992 NSAppleEventDescriptor *p =
993 [odbdesc paramDescriptorForKeyword:keyFileSender];
995 [dict setObject:[NSNumber numberWithUnsignedInt:[p typeCodeValue]]
998 p = [odbdesc paramDescriptorForKeyword:keyFileCustomPath];
1000 [dict setObject:[p stringValue] forKey:@"remotePath"];
1002 p = [odbdesc paramDescriptorForKeyword:keyFileSenderToken];
1004 [dict setObject:p forKey:@"remotePath"];
1007 // 2. Extract Xcode parameters (if any)
1008 NSAppleEventDescriptor *xcodedesc =
1009 [desc paramDescriptorForKeyword:keyAEPosition];
1012 MMSelectionRange *sr = (MMSelectionRange*)[[xcodedesc data] bytes];
1014 if (sr->lineNum < 0) {
1015 // Should select a range of lines.
1016 range.location = sr->startRange + 1;
1017 range.length = sr->endRange - sr->startRange + 1;
1019 // Should only move cursor to a line.
1020 range.location = sr->lineNum + 1;
1024 [dict setObject:NSStringFromRange(range) forKey:@"selectionRange"];
1027 // 3. Extract Spotlight search text (if any)
1028 NSAppleEventDescriptor *spotlightdesc =
1029 [desc paramDescriptorForKeyword:keyAESearchText];
1031 [dict setObject:[spotlightdesc stringValue] forKey:@"searchText"];
1036 - (void)passArguments:(NSDictionary *)args toVimController:(MMVimController*)vc
1040 // Pass filenames to open if required (the 'openFiles' argument can be used
1041 // to disallow opening of the files).
1042 NSArray *filenames = [args objectForKey:@"filenames"];
1043 if (filenames && [[args objectForKey:@"openFiles"] boolValue]) {
1044 NSString *tabDrop = buildTabDropCommand(filenames);
1045 [vc addVimInput:tabDrop];
1049 if (filenames && [args objectForKey:@"remoteID"]) {
1050 [vc odbEdit:filenames
1051 server:[[args objectForKey:@"remoteID"] unsignedIntValue]
1052 path:[args objectForKey:@"remotePath"]
1053 token:[args objectForKey:@"remoteToken"]];
1056 // Pass range of lines to select
1057 if ([args objectForKey:@"selectionRange"]) {
1058 NSRange selectionRange = NSRangeFromString(
1059 [args objectForKey:@"selectionRange"]);
1060 [vc addVimInput:buildSelectRangeCommand(selectionRange)];
1064 NSString *searchText = [args objectForKey:@"searchText"];
1066 [vc addVimInput:buildSearchTextCommand(searchText)];
1069 @end // MMAppController (Private)
1074 @implementation NSMenu (MMExtras)
1076 - (void)recurseSetAutoenablesItems:(BOOL)on
1078 [self setAutoenablesItems:on];
1080 int i, count = [self numberOfItems];
1081 for (i = 0; i < count; ++i) {
1082 NSMenuItem *item = [self itemAtIndex:i];
1083 [item setEnabled:YES];
1084 NSMenu *submenu = [item submenu];
1086 [submenu recurseSetAutoenablesItems:on];
1091 @end // NSMenu (MMExtras)
1096 @implementation NSNumber (MMExtras)
1099 return [self intValue];
1101 @end // NSNumber (MMExtras)