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"
36 #define MM_HANDLE_XCODE_MOD_EVENT 0
40 // Default timeout intervals on all connections.
41 static NSTimeInterval MMRequestTimeout = 5;
42 static NSTimeInterval MMReplyTimeout = 5;
45 #pragma options align=mac68k
48 short unused1; // 0 (not used)
49 short lineNum; // line to select (< 0 to specify range)
50 long startRange; // start of selection range (if line < 0)
51 long endRange; // end of selection range (if line < 0)
52 long unused2; // 0 (not used)
53 long theDate; // modification date/time
55 #pragma options align=reset
58 static int executeInLoginShell(NSString *path, NSArray *args);
61 @interface MMAppController (MMServices)
62 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
63 error:(NSString **)error;
64 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
65 error:(NSString **)error;
69 @interface MMAppController (Private)
70 - (MMVimController *)keyVimController;
71 - (MMVimController *)topmostVimController;
72 - (int)launchVimProcessWithArguments:(NSArray *)args;
73 - (NSArray *)filterFilesAndNotify:(NSArray *)files;
74 - (NSArray *)filterOpenFiles:(NSArray *)filenames
75 arguments:(NSDictionary *)args;
76 #if MM_HANDLE_XCODE_MOD_EVENT
77 - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
78 replyEvent:(NSAppleEventDescriptor *)reply;
80 - (int)findLaunchingProcessWithoutArguments;
81 - (MMVimController *)findUntitledWindow;
82 - (NSMutableDictionary *)extractArgumentsFromOdocEvent:
83 (NSAppleEventDescriptor *)desc;
84 - (void)passArguments:(NSDictionary *)args toVimController:(MMVimController*)vc;
87 @interface NSMenu (MMExtras)
88 - (void)recurseSetAutoenablesItems:(BOOL)on;
91 @interface NSNumber (MMExtras)
97 @implementation MMAppController
101 NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
102 [NSNumber numberWithBool:NO], MMNoWindowKey,
103 [NSNumber numberWithInt:64], MMTabMinWidthKey,
104 [NSNumber numberWithInt:6*64], MMTabMaxWidthKey,
105 [NSNumber numberWithInt:132], MMTabOptimumWidthKey,
106 [NSNumber numberWithInt:2], MMTextInsetLeftKey,
107 [NSNumber numberWithInt:1], MMTextInsetRightKey,
108 [NSNumber numberWithInt:1], MMTextInsetTopKey,
109 [NSNumber numberWithInt:1], MMTextInsetBottomKey,
110 [NSNumber numberWithBool:NO], MMTerminateAfterLastWindowClosedKey,
111 @"MMTypesetter", MMTypesetterKey,
112 [NSNumber numberWithFloat:1], MMCellWidthMultiplierKey,
113 [NSNumber numberWithFloat:-1], MMBaselineOffsetKey,
114 [NSNumber numberWithBool:YES], MMTranslateCtrlClickKey,
115 [NSNumber numberWithBool:NO], MMOpenFilesInTabsKey,
116 [NSNumber numberWithBool:NO], MMNoFontSubstitutionKey,
117 [NSNumber numberWithBool:NO], MMLoginShellKey,
118 [NSNumber numberWithBool:NO], MMAtsuiRendererKey,
119 [NSNumber numberWithInt:MMUntitledWindowAlways],
121 [NSNumber numberWithBool:NO], MMTexturedWindowKey,
122 [NSNumber numberWithBool:NO], MMZoomBothKey,
123 @"", MMLoginShellCommandKey,
124 @"", MMLoginShellArgumentKey,
127 [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
129 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
130 [NSApp registerServicesMenuSendTypes:types returnTypes:types];
135 if ((self = [super init])) {
136 fontContainerRef = loadFonts();
138 vimControllers = [NSMutableArray new];
139 pidArguments = [NSMutableDictionary new];
141 // NOTE! If the name of the connection changes here it must also be
142 // updated in MMBackend.m.
143 NSConnection *connection = [NSConnection defaultConnection];
144 NSString *name = [NSString stringWithFormat:@"%@-connection",
145 [[NSBundle mainBundle] bundleIdentifier]];
146 //NSLog(@"Registering connection with name '%@'", name);
147 if ([connection registerName:name]) {
148 [connection setRequestTimeout:MMRequestTimeout];
149 [connection setReplyTimeout:MMReplyTimeout];
150 [connection setRootObject:self];
152 // NOTE: When the user is resizing the window the AppKit puts the
153 // run loop in event tracking mode. Unless the connection listens
154 // to request in this mode, live resizing won't work.
155 [connection addRequestMode:NSEventTrackingRunLoopMode];
157 NSLog(@"WARNING: Failed to register connection with name '%@'",
167 //NSLog(@"MMAppController dealloc");
169 [pidArguments release]; pidArguments = nil;
170 [vimControllers release]; vimControllers = nil;
171 [openSelectionString release]; openSelectionString = nil;
176 #if MM_HANDLE_XCODE_MOD_EVENT
177 - (void)applicationWillFinishLaunching:(NSNotification *)notification
179 [[NSAppleEventManager sharedAppleEventManager]
181 andSelector:@selector(handleXcodeModEvent:replyEvent:)
187 - (void)applicationDidFinishLaunching:(NSNotification *)notification
189 [NSApp setServicesProvider:self];
192 - (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
194 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
195 NSAppleEventManager *aem = [NSAppleEventManager sharedAppleEventManager];
196 NSAppleEventDescriptor *desc = [aem currentAppleEvent];
198 // The user default MMUntitledWindow can be set to control whether an
199 // untitled window should open on 'Open' and 'Reopen' events.
200 int untitledWindowFlag = [ud integerForKey:MMUntitledWindowKey];
201 if ([desc eventID] == kAEOpenApplication
202 && (untitledWindowFlag & MMUntitledWindowOnOpen) == 0)
204 else if ([desc eventID] == kAEReopenApplication
205 && (untitledWindowFlag & MMUntitledWindowOnReopen) == 0)
208 // When a process is started from the command line, the 'Open' event will
209 // contain a parameter to surpress the opening of an untitled window.
210 desc = [desc paramDescriptorForKeyword:keyAEPropData];
211 desc = [desc paramDescriptorForKeyword:keyMMUntitledWindow];
212 if (desc && ![desc booleanValue])
215 // Never open an untitled window if there is at least one open window or if
216 // there are processes that are currently launching.
217 if ([vimControllers count] > 0 || [pidArguments count] > 0)
220 // NOTE! This way it possible to start the app with the command-line
221 // argument '-nowindow yes' and no window will be opened by default.
222 return ![ud boolForKey:MMNoWindowKey];
225 - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
227 [self newWindow:self];
231 - (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
233 // Opening files works like this:
234 // a) extract ODB/Xcode/Spotlight parameters from the current Apple event
235 // b) filter out any already open files (see filterOpenFiles::)
236 // c) open any remaining files
238 // A file is opened in an untitled window if there is one (it may be
239 // currently launching, or it may already be visible), otherwise a new
242 // Each launching Vim process has a dictionary of arguments that are passed
243 // to the process when in checks in (via connectBackend:pid:). The
244 // arguments for each launching process can be looked up by its PID (in the
245 // pidArguments dictionary).
247 NSMutableDictionary *arguments = [self extractArgumentsFromOdocEvent:
248 [[NSAppleEventManager sharedAppleEventManager] currentAppleEvent]];
250 // Filter out files that are already open
251 filenames = [self filterOpenFiles:filenames arguments:arguments];
253 // Open any files that remain
254 if ([filenames count]) {
256 BOOL openInTabs = [[NSUserDefaults standardUserDefaults]
257 boolForKey:MMOpenFilesInTabsKey];
259 [arguments setObject:filenames forKey:@"filenames"];
260 [arguments setObject:[NSNumber numberWithBool:YES] forKey:@"openFiles"];
262 if ((openInTabs && (vc = [self topmostVimController]))
263 || (vc = [self findUntitledWindow])) {
264 // Open files in an already open window.
265 [[[vc windowController] window] makeKeyAndOrderFront:self];
266 [self passArguments:arguments toVimController:vc];
268 // Open files in a launching Vim process or start a new process.
269 int pid = [self findLaunchingProcessWithoutArguments];
271 // Pass the filenames to the process straight away.
273 // TODO: It would be nicer if all arguments were passed to the
274 // Vim process in connectBackend::, but if we don't pass the
275 // filename arguments here, the window 'flashes' once when it
276 // opens. This is due to the 'welcome' screen first being
277 // displayed, then quickly thereafter the files are opened.
278 NSArray *fileArgs = [NSArray arrayWithObject:@"-p"];
279 fileArgs = [fileArgs arrayByAddingObjectsFromArray:filenames];
281 pid = [self launchVimProcessWithArguments:fileArgs];
284 // TODO: Notify user of failure?
285 [NSApp replyToOpenOrPrint:
286 NSApplicationDelegateReplyFailure];
290 // Make sure these files aren't opened again when
291 // connectBackend:pid: is called.
292 [arguments setObject:[NSNumber numberWithBool:NO]
293 forKey:@"openFiles"];
296 // TODO: If the Vim process fails to start, or if it changes PID,
297 // then the memory allocated for these parameters will leak.
298 // Ensure that this cannot happen or somehow detect it.
300 if ([arguments count] > 0)
301 [pidArguments setObject:arguments
302 forKey:[NSNumber numberWithInt:pid]];
306 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
307 // NSApplicationDelegateReplySuccess = 0,
308 // NSApplicationDelegateReplyCancel = 1,
309 // NSApplicationDelegateReplyFailure = 2
312 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
314 return [[NSUserDefaults standardUserDefaults]
315 boolForKey:MMTerminateAfterLastWindowClosedKey];
318 - (NSApplicationTerminateReply)applicationShouldTerminate:
319 (NSApplication *)sender
321 // TODO: Follow Apple's guidelines for 'Graceful Application Termination'
322 // (in particular, allow user to review changes and save).
323 int reply = NSTerminateNow;
324 BOOL modifiedBuffers = NO;
326 // Go through windows, checking for modified buffers. (Each Vim process
327 // tells MacVim when any buffer has been modified and MacVim sets the
328 // 'documentEdited' flag of the window correspondingly.)
329 NSEnumerator *e = [[NSApp windows] objectEnumerator];
331 while ((window = [e nextObject])) {
332 if ([window isDocumentEdited]) {
333 modifiedBuffers = YES;
338 if (modifiedBuffers) {
339 NSAlert *alert = [[NSAlert alloc] init];
340 [alert setAlertStyle:NSWarningAlertStyle];
341 [alert addButtonWithTitle:@"Quit"];
342 [alert addButtonWithTitle:@"Cancel"];
343 [alert setMessageText:@"Quit without saving?"];
344 [alert setInformativeText:@"There are modified buffers, "
345 "if you quit now all changes will be lost. Quit anyway?"];
347 if ([alert runModal] != NSAlertFirstButtonReturn)
348 reply = NSTerminateCancel;
352 // No unmodified buffers, but give a warning if there are multiple
353 // windows and/or tabs open.
354 int numWindows = [vimControllers count];
357 // Count the number of open tabs
358 e = [vimControllers objectEnumerator];
360 while ((vc = [e nextObject])) {
361 NSString *eval = [vc evaluateVimExpression:@"tabpagenr('$')"];
363 int count = [eval intValue];
364 if (count > 0 && count < INT_MAX)
369 if (numWindows > 1 || numTabs > 1) {
370 NSAlert *alert = [[NSAlert alloc] init];
371 [alert setAlertStyle:NSWarningAlertStyle];
372 [alert addButtonWithTitle:@"Quit"];
373 [alert addButtonWithTitle:@"Cancel"];
374 [alert setMessageText:@"Are you sure you want to quit MacVim?"];
376 NSString *info = nil;
377 if (numWindows > 1) {
378 if (numTabs > numWindows)
379 info = [NSString stringWithFormat:@"There are %d windows "
380 "open in MacVim, with a total of %d tabs. Do you want "
381 "to quit anyway?", numWindows, numTabs];
383 info = [NSString stringWithFormat:@"There are %d windows "
384 "open in MacVim. Do you want to quit anyway?",
388 info = [NSString stringWithFormat:@"There are %d tabs open "
389 "in MacVim. Do you want to quit anyway?", numTabs];
392 [alert setInformativeText:info];
394 if ([alert runModal] != NSAlertFirstButtonReturn)
395 reply = NSTerminateCancel;
402 // Tell all Vim processes to terminate now (otherwise they'll leave swap
404 if (NSTerminateNow == reply) {
405 e = [vimControllers objectEnumerator];
407 while ((vc = [e nextObject]))
408 [vc sendMessage:TerminateNowMsgID data:nil];
414 - (void)applicationWillTerminate:(NSNotification *)notification
416 #if MM_HANDLE_XCODE_MOD_EVENT
417 [[NSAppleEventManager sharedAppleEventManager]
418 removeEventHandlerForEventClass:'KAHL'
422 // This will invalidate all connections (since they were spawned from the
423 // default connection).
424 [[NSConnection defaultConnection] invalidate];
426 // Send a SIGINT to all running Vim processes, so that they are sure to
427 // receive the connectionDidDie: notification (a process has to be checking
428 // the run-loop for this to happen).
429 unsigned i, count = [vimControllers count];
430 for (i = 0; i < count; ++i) {
431 MMVimController *controller = [vimControllers objectAtIndex:i];
432 int pid = [controller pid];
437 if (fontContainerRef) {
438 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
439 fontContainerRef = 0;
442 [NSApp setDelegate:nil];
445 - (void)removeVimController:(id)controller
447 //NSLog(@"%s%@", _cmd, controller);
449 [[controller windowController] close];
451 [vimControllers removeObject:controller];
453 if (![vimControllers count]) {
454 // Turn on autoenabling of menus (because no Vim is open to handle it),
455 // but do not touch the MacVim menu. Note that the menus must be
456 // enabled first otherwise autoenabling does not work.
457 NSMenu *mainMenu = [NSApp mainMenu];
458 int i, count = [mainMenu numberOfItems];
459 for (i = 1; i < count; ++i) {
460 NSMenuItem *item = [mainMenu itemAtIndex:i];
461 [item setEnabled:YES];
462 [[item submenu] recurseSetAutoenablesItems:YES];
467 - (void)windowControllerWillOpen:(MMWindowController *)windowController
469 NSPoint topLeft = NSZeroPoint;
470 NSWindow *keyWin = [NSApp keyWindow];
471 NSWindow *win = [windowController window];
475 // If there is a key window, cascade from it, otherwise use the autosaved
476 // window position (if any).
478 NSRect frame = [keyWin frame];
479 topLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
481 NSString *topLeftString = [[NSUserDefaults standardUserDefaults]
482 stringForKey:MMTopLeftPointKey];
484 topLeft = NSPointFromString(topLeftString);
487 if (!NSEqualPoints(topLeft, NSZeroPoint)) {
489 topLeft = [win cascadeTopLeftFromPoint:topLeft];
491 [win setFrameTopLeftPoint:topLeft];
494 if (openSelectionString) {
495 // TODO: Pass this as a parameter instead! Get rid of
496 // 'openSelectionString' etc.
498 // There is some text to paste into this window as a result of the
499 // services menu "Open selection ..." being used.
500 [[windowController vimController] dropString:openSelectionString];
501 [openSelectionString release];
502 openSelectionString = nil;
506 - (IBAction)newWindow:(id)sender
508 [self launchVimProcessWithArguments:nil];
511 - (IBAction)fileOpen:(id)sender
513 NSOpenPanel *panel = [NSOpenPanel openPanel];
514 [panel setAllowsMultipleSelection:YES];
516 int result = [panel runModalForTypes:nil];
517 if (NSOKButton == result)
518 [self application:NSApp openFiles:[panel filenames]];
521 - (IBAction)selectNextWindow:(id)sender
523 unsigned i, count = [vimControllers count];
526 NSWindow *keyWindow = [NSApp keyWindow];
527 for (i = 0; i < count; ++i) {
528 MMVimController *vc = [vimControllers objectAtIndex:i];
529 if ([[[vc windowController] window] isEqual:keyWindow])
536 MMVimController *vc = [vimControllers objectAtIndex:i];
537 [[vc windowController] showWindow:self];
541 - (IBAction)selectPreviousWindow:(id)sender
543 unsigned i, count = [vimControllers count];
546 NSWindow *keyWindow = [NSApp keyWindow];
547 for (i = 0; i < count; ++i) {
548 MMVimController *vc = [vimControllers objectAtIndex:i];
549 if ([[[vc windowController] window] isEqual:keyWindow])
559 MMVimController *vc = [vimControllers objectAtIndex:i];
560 [[vc windowController] showWindow:self];
564 - (IBAction)fontSizeUp:(id)sender
566 [[NSFontManager sharedFontManager] modifyFont:
567 [NSNumber numberWithInt:NSSizeUpFontAction]];
570 - (IBAction)fontSizeDown:(id)sender
572 [[NSFontManager sharedFontManager] modifyFont:
573 [NSNumber numberWithInt:NSSizeDownFontAction]];
576 - (IBAction)orderFrontPreferencePanel:(id)sender
578 [[MMPreferenceController sharedPrefsWindowController] showWindow:self];
581 - (byref id <MMFrontendProtocol>)
582 connectBackend:(byref in id <MMBackendProtocol>)backend
585 //NSLog(@"Connect backend (pid=%d)", pid);
586 NSNumber *pidKey = [NSNumber numberWithInt:pid];
587 MMVimController *vc = nil;
590 [(NSDistantObject*)backend
591 setProtocolForProxy:@protocol(MMBackendProtocol)];
593 vc = [[[MMVimController alloc]
594 initWithBackend:backend pid:pid] autorelease];
596 if (![vimControllers count]) {
597 // The first window autosaves its position. (The autosaving
598 // features of Cocoa are not used because we need more control over
599 // what is autosaved and when it is restored.)
600 [[vc windowController] setWindowAutosaveKey:MMTopLeftPointKey];
603 [vimControllers addObject:vc];
605 id args = [pidArguments objectForKey:pidKey];
606 if (args && [NSNull null] != args)
607 [self passArguments:args toVimController:vc];
609 // HACK! MacVim does not get activated if it is launched from the
610 // terminal, so we forcibly activate here unless it is an untitled
611 // window opening. Untitled windows are treated differently, else
612 // MacVim would steal the focus if another app was activated while the
613 // untitled window was loading.
614 if (!args || args != [NSNull null])
615 [NSApp activateIgnoringOtherApps:YES];
618 [pidArguments removeObjectForKey:pidKey];
623 @catch (NSException *e) {
624 NSLog(@"Exception caught in %s: \"%@\"", _cmd, e);
627 [vimControllers removeObject:vc];
629 [pidArguments removeObjectForKey:pidKey];
635 - (NSArray *)serverList
637 NSMutableArray *array = [NSMutableArray array];
639 unsigned i, count = [vimControllers count];
640 for (i = 0; i < count; ++i) {
641 MMVimController *controller = [vimControllers objectAtIndex:i];
642 if ([controller serverName])
643 [array addObject:[controller serverName]];
649 @end // MMAppController
654 @implementation MMAppController (MMServices)
656 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
657 error:(NSString **)error
659 if (![[pboard types] containsObject:NSStringPboardType]) {
660 NSLog(@"WARNING: Pasteboard contains no object of type "
661 "NSStringPboardType");
665 MMVimController *vc = [self topmostVimController];
667 // Open a new tab first, since dropString: does not do this.
668 [vc sendMessage:AddNewTabMsgID data:nil];
669 [vc dropString:[pboard stringForType:NSStringPboardType]];
671 // NOTE: There is no window to paste the selection into, so save the
672 // text, open a new window, and paste the text when the next window
673 // opens. (If this is called several times in a row, then all but the
674 // last call might be ignored.)
675 if (openSelectionString) [openSelectionString release];
676 openSelectionString = [[pboard stringForType:NSStringPboardType] copy];
678 [self newWindow:self];
682 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
683 error:(NSString **)error
685 if (![[pboard types] containsObject:NSStringPboardType]) {
686 NSLog(@"WARNING: Pasteboard contains no object of type "
687 "NSStringPboardType");
691 // TODO: Parse multiple filenames and create array with names.
692 NSString *string = [pboard stringForType:NSStringPboardType];
693 string = [string stringByTrimmingCharactersInSet:
694 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
695 string = [string stringByStandardizingPath];
697 NSArray *filenames = [self filterFilesAndNotify:
698 [NSArray arrayWithObject:string]];
699 if ([filenames count] > 0) {
700 MMVimController *vc = nil;
701 if (userData && [userData isEqual:@"Tab"])
702 vc = [self topmostVimController];
705 [vc dropFiles:filenames forceOpen:YES];
707 [self application:NSApp openFiles:filenames];
712 @end // MMAppController (MMServices)
717 @implementation MMAppController (Private)
719 - (MMVimController *)keyVimController
721 NSWindow *keyWindow = [NSApp keyWindow];
723 unsigned i, count = [vimControllers count];
724 for (i = 0; i < count; ++i) {
725 MMVimController *vc = [vimControllers objectAtIndex:i];
726 if ([[[vc windowController] window] isEqual:keyWindow])
734 - (MMVimController *)topmostVimController
736 NSArray *windows = [NSApp orderedWindows];
737 if ([windows count] > 0) {
738 NSWindow *window = [windows objectAtIndex:0];
739 unsigned i, count = [vimControllers count];
740 for (i = 0; i < count; ++i) {
741 MMVimController *vc = [vimControllers objectAtIndex:i];
742 if ([[[vc windowController] window] isEqual:window])
750 - (int)launchVimProcessWithArguments:(NSArray *)args
753 NSString *path = [[NSBundle mainBundle] pathForAuxiliaryExecutable:@"Vim"];
756 NSLog(@"ERROR: Vim executable could not be found inside app bundle!");
760 NSArray *taskArgs = [NSArray arrayWithObjects:@"-g", @"-f", nil];
762 taskArgs = [taskArgs arrayByAddingObjectsFromArray:args];
764 BOOL useLoginShell = [[NSUserDefaults standardUserDefaults]
765 boolForKey:MMLoginShellKey];
767 // Run process with a login shell, roughly:
768 // echo "exec Vim -g -f args" | ARGV0=-`basename $SHELL` $SHELL [-l]
769 pid = executeInLoginShell(path, taskArgs);
771 // Run process directly:
773 NSTask *task = [NSTask launchedTaskWithLaunchPath:path
775 pid = task ? [task processIdentifier] : -1;
779 // NOTE: If the process has no arguments, then add a null argument to
780 // the pidArguments dictionary. This is later used to detect that a
781 // process without arguments is being launched.
783 [pidArguments setObject:[NSNull null]
784 forKey:[NSNumber numberWithInt:pid]];
786 NSLog(@"WARNING: %s%@ failed (useLoginShell=%d)", _cmd, args,
793 - (NSArray *)filterFilesAndNotify:(NSArray *)filenames
795 // Go trough 'filenames' array and make sure each file exists. Present
796 // warning dialog if some file was missing.
798 NSString *firstMissingFile = nil;
799 NSMutableArray *files = [NSMutableArray array];
800 unsigned i, count = [filenames count];
802 for (i = 0; i < count; ++i) {
803 NSString *name = [filenames objectAtIndex:i];
804 if ([[NSFileManager defaultManager] fileExistsAtPath:name]) {
805 [files addObject:name];
806 } else if (!firstMissingFile) {
807 firstMissingFile = name;
811 if (firstMissingFile) {
812 NSAlert *alert = [[NSAlert alloc] init];
813 [alert addButtonWithTitle:@"OK"];
816 if ([files count] >= count-1) {
817 [alert setMessageText:@"File not found"];
818 text = [NSString stringWithFormat:@"Could not open file with "
819 "name %@.", firstMissingFile];
821 [alert setMessageText:@"Multiple files not found"];
822 text = [NSString stringWithFormat:@"Could not open file with "
823 "name %@, and %d other files.", firstMissingFile,
824 count-[files count]-1];
827 [alert setInformativeText:text];
828 [alert setAlertStyle:NSWarningAlertStyle];
833 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
839 - (NSArray *)filterOpenFiles:(NSArray *)filenames
840 arguments:(NSDictionary *)args
842 // Check if any of the files in the 'filenames' array are open in any Vim
843 // process. Remove the files that are open from the 'filenames' array and
844 // return it. If all files were filtered out, then raise the first file in
845 // the Vim process it is open. Files that are filtered are sent an odb
846 // open event in case theID is not zero.
848 NSMutableDictionary *localArgs =
849 [NSMutableDictionary dictionaryWithDictionary:args];
850 MMVimController *raiseController = nil;
851 NSString *raiseFile = nil;
852 NSMutableArray *files = [filenames mutableCopy];
853 NSString *expr = [NSString stringWithFormat:
854 @"map([\"%@\"],\"bufloaded(v:val)\")",
855 [files componentsJoinedByString:@"\",\""]];
856 unsigned i, count = [vimControllers count];
858 // Ensure that the files aren't opened when passing arguments.
859 [localArgs setObject:[NSNumber numberWithBool:NO] forKey:@"openFiles"];
861 for (i = 0; i < count && [files count]; ++i) {
862 MMVimController *controller = [vimControllers objectAtIndex:i];
864 // Query Vim for which files in the 'files' array are open.
865 NSString *eval = [controller evaluateVimExpression:expr];
868 NSIndexSet *idxSet = [NSIndexSet indexSetWithVimList:eval];
869 if ([idxSet count]) {
871 // Remember the file and which Vim that has it open so that
872 // we can raise it later on.
873 raiseController = controller;
874 raiseFile = [files objectAtIndex:[idxSet firstIndex]];
875 [[raiseFile retain] autorelease];
878 // Pass (ODB/Xcode/Spotlight) arguments to this process.
879 [localArgs setObject:[files objectsAtIndexes:idxSet]
880 forKey:@"filenames"];
881 [self passArguments:localArgs toVimController:controller];
883 // Remove all the files that were open in this Vim process and
884 // create a new expression to evaluate.
885 [files removeObjectsAtIndexes:idxSet];
886 expr = [NSString stringWithFormat:
887 @"map([\"%@\"],\"bufloaded(v:val)\")",
888 [files componentsJoinedByString:@"\",\""]];
892 if (![files count] && raiseFile) {
893 // Raise the window containing the first file that was already open,
894 // and make sure that the tab containing that file is selected. Only
895 // do this if there are no more files to open, otherwise sometimes the
896 // window with 'raiseFile' will be raised, other times it might be the
897 // window that will open with the files in the 'files' array.
898 raiseFile = [raiseFile stringByEscapingSpecialFilenameCharacters];
899 NSString *input = [NSString stringWithFormat:@"<C-\\><C-N>"
900 ":let oldswb=&swb|let &swb=\"useopen,usetab\"|"
901 "tab sb %@|let &swb=oldswb|unl oldswb|"
902 "cal foreground()|redr|f<CR>", raiseFile];
904 [raiseController addVimInput:input];
910 #if MM_HANDLE_XCODE_MOD_EVENT
911 - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
912 replyEvent:(NSAppleEventDescriptor *)reply
915 // Xcode sends this event to query MacVim which open files have been
917 NSLog(@"reply:%@", reply);
918 NSLog(@"event:%@", event);
920 NSEnumerator *e = [vimControllers objectEnumerator];
922 while ((vc = [e nextObject])) {
923 DescType type = [reply descriptorType];
924 unsigned len = [[type data] length];
925 NSMutableData *data = [NSMutableData data];
927 [data appendBytes:&type length:sizeof(DescType)];
928 [data appendBytes:&len length:sizeof(unsigned)];
929 [data appendBytes:[reply data] length:len];
931 [vc sendMessage:XcodeModMsgID data:data];
937 - (int)findLaunchingProcessWithoutArguments
939 NSArray *keys = [pidArguments allKeysForObject:[NSNull null]];
940 if ([keys count] > 0) {
941 //NSLog(@"found launching process without arguments");
942 return [[keys objectAtIndex:0] intValue];
948 - (MMVimController *)findUntitledWindow
950 NSEnumerator *e = [vimControllers objectEnumerator];
952 while ((vc = [e nextObject])) {
953 // TODO: This is a moronic test...should query the Vim process if there
954 // are any open buffers or something like that instead.
955 NSString *title = [[[vc windowController] window] title];
956 if ([title hasPrefix:@"[No Name] - VIM"]) {
957 //NSLog(@"found untitled window");
965 - (NSMutableDictionary *)extractArgumentsFromOdocEvent:
966 (NSAppleEventDescriptor *)desc
968 NSMutableDictionary *dict = [NSMutableDictionary dictionary];
970 // 1. Extract ODB parameters (if any)
971 NSAppleEventDescriptor *odbdesc = desc;
972 if (![odbdesc paramDescriptorForKeyword:keyFileSender]) {
973 // The ODB paramaters may hide inside the 'keyAEPropData' descriptor.
974 odbdesc = [odbdesc paramDescriptorForKeyword:keyAEPropData];
975 if (![odbdesc paramDescriptorForKeyword:keyFileSender])
980 NSAppleEventDescriptor *p =
981 [odbdesc paramDescriptorForKeyword:keyFileSender];
983 [dict setObject:[NSNumber numberWithUnsignedInt:[p typeCodeValue]]
986 p = [odbdesc paramDescriptorForKeyword:keyFileCustomPath];
988 [dict setObject:[p stringValue] forKey:@"remotePath"];
990 p = [odbdesc paramDescriptorForKeyword:keyFileSenderToken];
992 [dict setObject:p forKey:@"remotePath"];
995 // 2. Extract Xcode parameters (if any)
996 NSAppleEventDescriptor *xcodedesc =
997 [desc paramDescriptorForKeyword:keyAEPosition];
1000 MMSelectionRange *sr = (MMSelectionRange*)[[xcodedesc data] bytes];
1002 if (sr->lineNum < 0) {
1003 // Should select a range of lines.
1004 range.location = sr->startRange + 1;
1005 range.length = sr->endRange - sr->startRange + 1;
1007 // Should only move cursor to a line.
1008 range.location = sr->lineNum + 1;
1012 [dict setObject:NSStringFromRange(range) forKey:@"selectionRange"];
1015 // 3. Extract Spotlight search text (if any)
1016 NSAppleEventDescriptor *spotlightdesc =
1017 [desc paramDescriptorForKeyword:keyAESearchText];
1019 [dict setObject:[spotlightdesc stringValue] forKey:@"searchText"];
1024 - (void)passArguments:(NSDictionary *)args toVimController:(MMVimController*)vc
1028 // Pass filenames to open if required (the 'openFiles' argument can be used
1029 // to disallow opening of the files).
1030 NSArray *filenames = [args objectForKey:@"filenames"];
1031 if (filenames && [[args objectForKey:@"openFiles"] boolValue]) {
1032 NSString *tabDrop = buildTabDropCommand(filenames);
1033 [vc addVimInput:tabDrop];
1037 if (filenames && [args objectForKey:@"remoteID"]) {
1038 [vc odbEdit:filenames
1039 server:[[args objectForKey:@"remoteID"] unsignedIntValue]
1040 path:[args objectForKey:@"remotePath"]
1041 token:[args objectForKey:@"remoteToken"]];
1044 // Pass range of lines to select
1045 if ([args objectForKey:@"selectionRange"]) {
1046 NSRange selectionRange = NSRangeFromString(
1047 [args objectForKey:@"selectionRange"]);
1048 [vc addVimInput:buildSelectRangeCommand(selectionRange)];
1052 NSString *searchText = [args objectForKey:@"searchText"];
1054 [vc addVimInput:buildSearchTextCommand(searchText)];
1057 @end // MMAppController (Private)
1062 @implementation NSMenu (MMExtras)
1064 - (void)recurseSetAutoenablesItems:(BOOL)on
1066 [self setAutoenablesItems:on];
1068 int i, count = [self numberOfItems];
1069 for (i = 0; i < count; ++i) {
1070 NSMenuItem *item = [self itemAtIndex:i];
1071 [item setEnabled:YES];
1072 NSMenu *submenu = [item submenu];
1074 [submenu recurseSetAutoenablesItems:on];
1079 @end // NSMenu (MMExtras)
1084 @implementation NSNumber (MMExtras)
1087 return [self intValue];
1089 @end // NSNumber (MMExtras)
1095 executeInLoginShell(NSString *path, NSArray *args)
1097 // Start a login shell and execute the command 'path' with arguments 'args'
1098 // in the shell. This ensures that user environment variables are set even
1099 // when MacVim was started from the Finder.
1102 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
1104 // Determine which shell to use to execute the command. The user
1105 // may decide which shell to use by setting a user default or the
1106 // $SHELL environment variable.
1107 NSString *shell = [ud stringForKey:MMLoginShellCommandKey];
1108 if (!shell || [shell length] == 0)
1109 shell = [[[NSProcessInfo processInfo] environment]
1110 objectForKey:@"SHELL"];
1112 shell = @"/bin/bash";
1114 //NSLog(@"shell = %@", shell);
1116 // Bash needs the '-l' flag to launch a login shell. The user may add
1117 // flags by setting a user default.
1118 NSString *shellArgument = [ud stringForKey:MMLoginShellArgumentKey];
1119 if (!shellArgument || [shellArgument length] == 0) {
1120 if ([[shell lastPathComponent] isEqual:@"bash"])
1121 shellArgument = @"-l";
1123 shellArgument = nil;
1126 //NSLog(@"shellArgument = %@", shellArgument);
1128 // Build input string to pipe to the login shell.
1129 NSMutableString *input = [NSMutableString stringWithFormat:
1130 @"exec \"%@\"", path];
1132 // Append all arguments, making sure they are properly quoted, even
1133 // when they contain single quotes.
1134 NSEnumerator *e = [args objectEnumerator];
1137 while ((obj = [e nextObject])) {
1138 NSMutableString *arg = [NSMutableString stringWithString:obj];
1139 [arg replaceOccurrencesOfString:@"'" withString:@"'\"'\"'"
1140 options:NSLiteralSearch
1141 range:NSMakeRange(0, [arg length])];
1142 [input appendFormat:@" '%@'", arg];
1146 // Build the argument vector used to start the login shell.
1147 NSString *shellArg0 = [NSString stringWithFormat:@"-%@",
1148 [shell lastPathComponent]];
1149 char *shellArgv[3] = { (char *)[shellArg0 UTF8String], NULL, NULL };
1151 shellArgv[1] = (char *)[shellArgument UTF8String];
1153 // Get the C string representation of the shell path before the fork since
1154 // we must not call Foundation functions after a fork.
1155 char *shellPath = [shell fileSystemRepresentation];
1157 // Fork and execute the process.
1159 if (pipe(ds)) return -1;
1164 } else if (pid == 0) {
1166 if (close(ds[1]) == -1) exit(255);
1167 if (dup2(ds[0], 0) == -1) exit(255);
1169 execv(shellPath, shellArgv);
1171 // Never reached unless execv fails
1175 if (close(ds[0]) == -1) return -1;
1177 // Send input to execute to the child process
1178 [input appendString:@"\n"];
1179 int bytes = [input lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
1181 if (write(ds[1], [input UTF8String], bytes) != bytes) return -1;
1182 if (close(ds[1]) == -1) return -1;