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"
34 #define MM_HANDLE_XCODE_MOD_EVENT 0
38 // Default timeout intervals on all connections.
39 static NSTimeInterval MMRequestTimeout = 5;
40 static NSTimeInterval MMReplyTimeout = 5;
43 #pragma options align=mac68k
46 short unused1; // 0 (not used)
47 short lineNum; // line to select (< 0 to specify range)
48 long startRange; // start of selection range (if line < 0)
49 long endRange; // end of selection range (if line < 0)
50 long unused2; // 0 (not used)
51 long theDate; // modification date/time
53 #pragma options align=reset
56 @interface MMAppController (MMServices)
57 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
58 error:(NSString **)error;
59 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
60 error:(NSString **)error;
64 @interface MMAppController (Private)
65 - (MMVimController *)keyVimController;
66 - (MMVimController *)topmostVimController;
67 - (int)launchVimProcessWithArguments:(NSArray *)args;
68 - (NSArray *)filterFilesAndNotify:(NSArray *)files;
69 - (NSArray *)filterOpenFiles:(NSArray *)filenames remote:(OSType)theID
71 token:(NSAppleEventDescriptor *)token
72 selectionRange:(NSRange)selectionRange;
73 #if MM_HANDLE_XCODE_MOD_EVENT
74 - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
75 replyEvent:(NSAppleEventDescriptor *)reply;
77 - (int)findLaunchingProcessWithoutArguments;
78 - (MMVimController *)findUntitledWindow;
81 @interface NSMenu (MMExtras)
82 - (void)recurseSetAutoenablesItems:(BOOL)on;
85 @interface NSNumber (MMExtras)
91 @implementation MMAppController
95 NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
96 [NSNumber numberWithBool:NO], MMNoWindowKey,
97 [NSNumber numberWithInt:64], MMTabMinWidthKey,
98 [NSNumber numberWithInt:6*64], MMTabMaxWidthKey,
99 [NSNumber numberWithInt:132], MMTabOptimumWidthKey,
100 [NSNumber numberWithInt:2], MMTextInsetLeftKey,
101 [NSNumber numberWithInt:1], MMTextInsetRightKey,
102 [NSNumber numberWithInt:1], MMTextInsetTopKey,
103 [NSNumber numberWithInt:1], MMTextInsetBottomKey,
104 [NSNumber numberWithBool:NO], MMTerminateAfterLastWindowClosedKey,
105 @"MMTypesetter", MMTypesetterKey,
106 [NSNumber numberWithFloat:1], MMCellWidthMultiplierKey,
107 [NSNumber numberWithFloat:-1], MMBaselineOffsetKey,
108 [NSNumber numberWithBool:YES], MMTranslateCtrlClickKey,
109 [NSNumber numberWithBool:NO], MMOpenFilesInTabsKey,
110 [NSNumber numberWithBool:NO], MMNoFontSubstitutionKey,
111 [NSNumber numberWithBool:NO], MMLoginShellKey,
112 [NSNumber numberWithBool:NO], MMAtsuiRendererKey,
113 [NSNumber numberWithInt:MMUntitledWindowAlways],
117 [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
119 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
120 [NSApp registerServicesMenuSendTypes:types returnTypes:types];
125 if ((self = [super init])) {
126 fontContainerRef = loadFonts();
128 vimControllers = [NSMutableArray new];
129 pidArguments = [NSMutableDictionary new];
131 // NOTE! If the name of the connection changes here it must also be
132 // updated in MMBackend.m.
133 NSConnection *connection = [NSConnection defaultConnection];
134 NSString *name = [NSString stringWithFormat:@"%@-connection",
135 [[NSBundle mainBundle] bundleIdentifier]];
136 //NSLog(@"Registering connection with name '%@'", name);
137 if ([connection registerName:name]) {
138 [connection setRequestTimeout:MMRequestTimeout];
139 [connection setReplyTimeout:MMReplyTimeout];
140 [connection setRootObject:self];
142 // NOTE: When the user is resizing the window the AppKit puts the
143 // run loop in event tracking mode. Unless the connection listens
144 // to request in this mode, live resizing won't work.
145 [connection addRequestMode:NSEventTrackingRunLoopMode];
147 NSLog(@"WARNING: Failed to register connection with name '%@'",
157 //NSLog(@"MMAppController dealloc");
159 [pidArguments release]; pidArguments = nil;
160 [vimControllers release]; vimControllers = nil;
161 [openSelectionString release]; openSelectionString = nil;
166 #if MM_HANDLE_XCODE_MOD_EVENT
167 - (void)applicationWillFinishLaunching:(NSNotification *)notification
169 [[NSAppleEventManager sharedAppleEventManager]
171 andSelector:@selector(handleXcodeModEvent:replyEvent:)
177 - (void)applicationDidFinishLaunching:(NSNotification *)notification
179 [NSApp setServicesProvider:self];
182 - (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
184 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
185 NSAppleEventManager *aem = [NSAppleEventManager sharedAppleEventManager];
186 NSAppleEventDescriptor *desc = [aem currentAppleEvent];
188 // The user default MMUntitledWindow can be set to control whether an
189 // untitled window should open on 'Open' and 'Reopen' events.
190 int untitledWindowFlag = [ud integerForKey:MMUntitledWindowKey];
191 if ([desc eventID] == kAEOpenApplication
192 && (untitledWindowFlag & MMUntitledWindowOnOpen) == 0)
194 else if ([desc eventID] == kAEReopenApplication
195 && (untitledWindowFlag & MMUntitledWindowOnReopen) == 0)
198 // When a process is started from the command line, the 'Open' event will
199 // contain a parameter to surpress the opening of an untitled window.
200 desc = [desc paramDescriptorForKeyword:keyAEPropData];
201 desc = [desc paramDescriptorForKeyword:keyMMUntitledWindow];
202 if (desc && ![desc booleanValue])
205 // Never open an untitled window if there is at least one open window or if
206 // there are processes that are currently launching.
207 if ([vimControllers count] > 0 || [pidArguments count] > 0)
210 // NOTE! This way it possible to start the app with the command-line
211 // argument '-nowindow yes' and no window will be opened by default.
212 return ![ud boolForKey:MMNoWindowKey];
215 - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
217 [self newWindow:self];
221 - (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
223 // Opening files works like this:
224 // a) extract ODB and/or Xcode parameters from the current Apple event
225 // b) filter out any already open files (see filterOpenFiles:::::)
226 // c) open any remaining files
228 // A file is opened in an untitled window if there is one (it may be
229 // currently launching, or it may already be visible), otherwise a new
232 // Each launching Vim process has a dictionary of arguments that are passed
233 // to the process when in checks in (via connectBackend:pid:). The
234 // arguments for each launching process can be looked up by its PID (in the
235 // pidArguments dictionary).
238 NSString *remotePath = nil;
239 NSAppleEventManager *aem = nil;
240 NSAppleEventDescriptor *remoteToken = nil;
241 NSAppleEventDescriptor *odbdesc = nil;
242 NSAppleEventDescriptor *xcodedesc = nil;
243 NSRange selectionRange = { NSNotFound, 0 };
245 // 1. Extract ODB parameters (if any)
246 aem = [NSAppleEventManager sharedAppleEventManager];
247 odbdesc = [aem currentAppleEvent];
248 if (![odbdesc paramDescriptorForKeyword:keyFileSender]) {
249 // The ODB paramaters may hide inside the 'keyAEPropData' descriptor.
250 odbdesc = [odbdesc paramDescriptorForKeyword:keyAEPropData];
251 if (![odbdesc paramDescriptorForKeyword:keyFileSender])
256 remoteID = [[odbdesc paramDescriptorForKeyword:keyFileSender]
258 remotePath = [[odbdesc paramDescriptorForKeyword:keyFileCustomPath]
260 remoteToken = [[odbdesc paramDescriptorForKeyword:keyFileSenderToken]
264 // 2. Extract Xcode parameters (if any)
265 xcodedesc = [[aem currentAppleEvent]
266 paramDescriptorForKeyword:keyAEPosition];
268 MMSelectionRange *sr = (MMSelectionRange*)[[xcodedesc data] bytes];
269 if (sr->lineNum < 0) {
270 // Should select a range of lines.
271 selectionRange.location = sr->startRange + 1;
272 selectionRange.length = sr->endRange - sr->startRange + 1;
274 // Should only move cursor to a line.
275 selectionRange.location = sr->lineNum + 1;
279 // 3. Filter out files that are already open
280 filenames = [self filterOpenFiles:filenames remote:remoteID path:remotePath
282 selectionRange:selectionRange];
284 // 4. Open any files that remain
285 if ([filenames count]) {
287 BOOL openInTabs = [[NSUserDefaults standardUserDefaults]
288 boolForKey:MMOpenFilesInTabsKey];
290 if ((openInTabs && (vc = [self topmostVimController]))
291 || (vc = [self findUntitledWindow])) {
293 // 5-1 Open files in an already open window (either due to the fact
294 // that MMMOpenFilesInTabs was set or because there was already an
295 // untitled Vim process open).
297 [vc dropFiles:filenames forceOpen:YES];
299 [vc odbEdit:filenames server:remoteID path:remotePath
301 if (selectionRange.location != NSNotFound)
302 [vc addVimInput:buildSelectRangeCommand(selectionRange)];
306 // a) in a launching Vim that has no arguments, if there is one
308 // b) launch a new Vim process and open files there.
310 NSMutableDictionary *argDict = [NSMutableDictionary dictionary];
312 int pid = [self findLaunchingProcessWithoutArguments];
314 // The filenames are passed as arguments in connectBackend::.
315 [argDict setObject:filenames forKey:@"filenames"];
317 // Pass the filenames to the process straight away.
319 // TODO: It would be nicer if all arguments were passed to the
320 // Vim process in connectBackend::, but if we don't pass the
321 // filename arguments here, the window 'flashes' once when it
322 // opens. This is due to the 'welcome' screen first being
323 // displayed, then quickly thereafter the files are opened.
324 NSArray *fileArgs = [NSArray arrayWithObject:@"-p"];
325 fileArgs = [fileArgs arrayByAddingObjectsFromArray:filenames];
327 pid = [self launchVimProcessWithArguments:fileArgs];
330 // TODO: If the Vim process fails to start, or if it changes PID,
331 // then the memory allocated for these parameters will leak.
332 // Ensure that this cannot happen or somehow detect it.
335 [argDict setObject:filenames forKey:@"remoteFiles"];
337 // The remote token can be arbitrary data so it is cannot
338 // (without encoding it as text) be passed on the command line.
339 [argDict setObject:[NSNumber numberWithUnsignedInt:remoteID]
342 [argDict setObject:remotePath forKey:@"remotePath"];
344 [argDict setObject:remoteToken forKey:@"remoteToken"];
347 if (selectionRange.location != NSNotFound)
348 [argDict setObject:NSStringFromRange(selectionRange)
349 forKey:@"selectionRange"];
351 if ([argDict count] > 0)
352 [pidArguments setObject:argDict
353 forKey:[NSNumber numberWithInt:pid]];
357 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
358 // NSApplicationDelegateReplySuccess = 0,
359 // NSApplicationDelegateReplyCancel = 1,
360 // NSApplicationDelegateReplyFailure = 2
363 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
365 return [[NSUserDefaults standardUserDefaults]
366 boolForKey:MMTerminateAfterLastWindowClosedKey];
369 - (NSApplicationTerminateReply)applicationShouldTerminate:
370 (NSApplication *)sender
372 // TODO: Follow Apple's guidelines for 'Graceful Application Termination'
373 // (in particular, allow user to review changes and save).
374 int reply = NSTerminateNow;
375 BOOL modifiedBuffers = NO;
377 // Go through windows, checking for modified buffers. (Each Vim process
378 // tells MacVim when any buffer has been modified and MacVim sets the
379 // 'documentEdited' flag of the window correspondingly.)
380 NSEnumerator *e = [[NSApp windows] objectEnumerator];
382 while ((window = [e nextObject])) {
383 if ([window isDocumentEdited]) {
384 modifiedBuffers = YES;
389 if (modifiedBuffers) {
390 NSAlert *alert = [[NSAlert alloc] init];
391 [alert addButtonWithTitle:@"Quit"];
392 [alert addButtonWithTitle:@"Cancel"];
393 [alert setMessageText:@"Quit without saving?"];
394 [alert setInformativeText:@"There are modified buffers, "
395 "if you quit now all changes will be lost. Quit anyway?"];
396 [alert setAlertStyle:NSWarningAlertStyle];
398 if ([alert runModal] != NSAlertFirstButtonReturn)
399 reply = NSTerminateCancel;
404 // Tell all Vim processes to terminate now (otherwise they'll leave swap
406 if (NSTerminateNow == reply) {
407 e = [vimControllers objectEnumerator];
409 while ((vc = [e nextObject]))
410 [vc sendMessage:TerminateNowMsgID data:nil];
416 - (void)applicationWillTerminate:(NSNotification *)notification
418 #if MM_HANDLE_XCODE_MOD_EVENT
419 [[NSAppleEventManager sharedAppleEventManager]
420 removeEventHandlerForEventClass:'KAHL'
424 // This will invalidate all connections (since they were spawned from the
425 // default connection).
426 [[NSConnection defaultConnection] invalidate];
428 // Send a SIGINT to all running Vim processes, so that they are sure to
429 // receive the connectionDidDie: notification (a process has to be checking
430 // the run-loop for this to happen).
431 unsigned i, count = [vimControllers count];
432 for (i = 0; i < count; ++i) {
433 MMVimController *controller = [vimControllers objectAtIndex:i];
434 int pid = [controller pid];
439 if (fontContainerRef) {
440 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
441 fontContainerRef = 0;
444 // TODO: Is this a correct way of releasing the MMAppController?
445 // (It doesn't seem like dealloc is ever called.)
446 [NSApp setDelegate:nil];
450 - (void)removeVimController:(id)controller
452 //NSLog(@"%s%@", _cmd, controller);
454 [[controller windowController] close];
456 [vimControllers removeObject:controller];
458 if (![vimControllers count]) {
459 // Turn on autoenabling of menus (because no Vim is open to handle it),
460 // but do not touch the MacVim menu. Note that the menus must be
461 // enabled first otherwise autoenabling does not work.
462 NSMenu *mainMenu = [NSApp mainMenu];
463 int i, count = [mainMenu numberOfItems];
464 for (i = 1; i < count; ++i) {
465 NSMenuItem *item = [mainMenu itemAtIndex:i];
466 [item setEnabled:YES];
467 [[item submenu] recurseSetAutoenablesItems:YES];
472 - (void)windowControllerWillOpen:(MMWindowController *)windowController
474 NSPoint topLeft = NSZeroPoint;
475 NSWindow *keyWin = [NSApp keyWindow];
476 NSWindow *win = [windowController window];
480 // If there is a key window, cascade from it, otherwise use the autosaved
481 // window position (if any).
483 NSRect frame = [keyWin frame];
484 topLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
486 NSString *topLeftString = [[NSUserDefaults standardUserDefaults]
487 stringForKey:MMTopLeftPointKey];
489 topLeft = NSPointFromString(topLeftString);
492 if (!NSEqualPoints(topLeft, NSZeroPoint)) {
494 topLeft = [win cascadeTopLeftFromPoint:topLeft];
496 [win setFrameTopLeftPoint:topLeft];
499 if (openSelectionString) {
500 // TODO: Pass this as a parameter instead! Get rid of
501 // 'openSelectionString' etc.
503 // There is some text to paste into this window as a result of the
504 // services menu "Open selection ..." being used.
505 [[windowController vimController] dropString:openSelectionString];
506 [openSelectionString release];
507 openSelectionString = nil;
511 - (IBAction)newWindow:(id)sender
513 [self launchVimProcessWithArguments:nil];
516 - (IBAction)fileOpen:(id)sender
518 NSOpenPanel *panel = [NSOpenPanel openPanel];
519 [panel setAllowsMultipleSelection:YES];
521 int result = [panel runModalForTypes:nil];
522 if (NSOKButton == result)
523 [self application:NSApp openFiles:[panel filenames]];
526 - (IBAction)selectNextWindow:(id)sender
528 unsigned i, count = [vimControllers count];
531 NSWindow *keyWindow = [NSApp keyWindow];
532 for (i = 0; i < count; ++i) {
533 MMVimController *vc = [vimControllers objectAtIndex:i];
534 if ([[[vc windowController] window] isEqual:keyWindow])
541 MMVimController *vc = [vimControllers objectAtIndex:i];
542 [[vc windowController] showWindow:self];
546 - (IBAction)selectPreviousWindow:(id)sender
548 unsigned i, count = [vimControllers count];
551 NSWindow *keyWindow = [NSApp keyWindow];
552 for (i = 0; i < count; ++i) {
553 MMVimController *vc = [vimControllers objectAtIndex:i];
554 if ([[[vc windowController] window] isEqual:keyWindow])
564 MMVimController *vc = [vimControllers objectAtIndex:i];
565 [[vc windowController] showWindow:self];
569 - (IBAction)fontSizeUp:(id)sender
571 [[NSFontManager sharedFontManager] modifyFont:
572 [NSNumber numberWithInt:NSSizeUpFontAction]];
575 - (IBAction)fontSizeDown:(id)sender
577 [[NSFontManager sharedFontManager] modifyFont:
578 [NSNumber numberWithInt:NSSizeDownFontAction]];
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 // Pass arguments to the Vim process.
606 id args = [pidArguments objectForKey:pidKey];
607 if (args && args != [NSNull null]) {
608 // Pass filenames to open
609 NSArray *filenames = [args objectForKey:@"filenames"];
611 NSString *tabDrop = buildTabDropCommand(filenames);
612 [vc addVimInput:tabDrop];
616 if ([args objectForKey:@"remoteFiles"]
617 && [args objectForKey:@"remoteID"]) {
618 [vc odbEdit:[args objectForKey:@"remoteFiles"]
619 server:[[args objectForKey:@"remoteID"] unsignedIntValue]
620 path:[args objectForKey:@"remotePath"]
621 token:[args objectForKey:@"remoteToken"]];
624 // Pass range of lines to select
625 if ([args objectForKey:@"selectionRange"]) {
626 NSRange selectionRange = NSRangeFromString(
627 [args objectForKey:@"selectionRange"]);
628 [vc addVimInput:buildSelectRangeCommand(selectionRange)];
632 // HACK! MacVim does not get activated if it is launched from the
633 // terminal, so we forcibly activate here unless it is an untitled
634 // window opening. Untitled windows are treated differently, else
635 // MacVim would steal the focus if another app was activated while the
636 // untitled window was loading.
637 if (!args || args != [NSNull null])
638 [NSApp activateIgnoringOtherApps:YES];
641 [pidArguments removeObjectForKey:pidKey];
646 @catch (NSException *e) {
647 NSLog(@"Exception caught in %s: \"%@\"", _cmd, e);
650 [vimControllers removeObject:vc];
652 [pidArguments removeObjectForKey:pidKey];
658 - (NSArray *)serverList
660 NSMutableArray *array = [NSMutableArray array];
662 unsigned i, count = [vimControllers count];
663 for (i = 0; i < count; ++i) {
664 MMVimController *controller = [vimControllers objectAtIndex:i];
665 if ([controller serverName])
666 [array addObject:[controller serverName]];
672 @end // MMAppController
677 @implementation MMAppController (MMServices)
679 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
680 error:(NSString **)error
682 if (![[pboard types] containsObject:NSStringPboardType]) {
683 NSLog(@"WARNING: Pasteboard contains no object of type "
684 "NSStringPboardType");
688 MMVimController *vc = [self topmostVimController];
690 // Open a new tab first, since dropString: does not do this.
691 [vc sendMessage:AddNewTabMsgID data:nil];
692 [vc dropString:[pboard stringForType:NSStringPboardType]];
694 // NOTE: There is no window to paste the selection into, so save the
695 // text, open a new window, and paste the text when the next window
696 // opens. (If this is called several times in a row, then all but the
697 // last call might be ignored.)
698 if (openSelectionString) [openSelectionString release];
699 openSelectionString = [[pboard stringForType:NSStringPboardType] copy];
701 [self newWindow:self];
705 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
706 error:(NSString **)error
708 if (![[pboard types] containsObject:NSStringPboardType]) {
709 NSLog(@"WARNING: Pasteboard contains no object of type "
710 "NSStringPboardType");
714 // TODO: Parse multiple filenames and create array with names.
715 NSString *string = [pboard stringForType:NSStringPboardType];
716 string = [string stringByTrimmingCharactersInSet:
717 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
718 string = [string stringByStandardizingPath];
720 NSArray *filenames = [self filterFilesAndNotify:
721 [NSArray arrayWithObject:string]];
722 if ([filenames count] > 0) {
723 MMVimController *vc = nil;
724 if (userData && [userData isEqual:@"Tab"])
725 vc = [self topmostVimController];
728 [vc dropFiles:filenames forceOpen:YES];
730 [self application:NSApp openFiles:filenames];
735 @end // MMAppController (MMServices)
740 @implementation MMAppController (Private)
742 - (MMVimController *)keyVimController
744 NSWindow *keyWindow = [NSApp keyWindow];
746 unsigned i, count = [vimControllers count];
747 for (i = 0; i < count; ++i) {
748 MMVimController *vc = [vimControllers objectAtIndex:i];
749 if ([[[vc windowController] window] isEqual:keyWindow])
757 - (MMVimController *)topmostVimController
759 NSArray *windows = [NSApp orderedWindows];
760 if ([windows count] > 0) {
761 NSWindow *window = [windows objectAtIndex:0];
762 unsigned i, count = [vimControllers count];
763 for (i = 0; i < count; ++i) {
764 MMVimController *vc = [vimControllers objectAtIndex:i];
765 if ([[[vc windowController] window] isEqual:window])
773 - (int)launchVimProcessWithArguments:(NSArray *)args
775 NSString *taskPath = nil;
776 NSArray *taskArgs = nil;
777 NSString *path = [[NSBundle mainBundle] pathForAuxiliaryExecutable:@"Vim"];
780 NSLog(@"ERROR: Vim executable could not be found inside app bundle!");
784 if ([[NSUserDefaults standardUserDefaults] boolForKey:MMLoginShellKey]) {
785 // Run process with a login shell
786 // $SHELL -l -c "exec Vim -g -f args"
787 // (-g for GUI, -f for foreground, i.e. don't fork)
789 NSMutableString *execArg = [NSMutableString
790 stringWithFormat:@"exec \"%@\" -g -f", path];
792 // Append all arguments while making sure that arguments containing
793 // spaces are enclosed in quotes.
794 NSCharacterSet *space = [NSCharacterSet whitespaceCharacterSet];
795 unsigned i, count = [args count];
797 for (i = 0; i < count; ++i) {
798 NSString *arg = [args objectAtIndex:i];
799 if (NSNotFound != [arg rangeOfCharacterFromSet:space].location)
800 [execArg appendFormat:@" \"%@\"", arg];
802 [execArg appendFormat:@" %@", arg];
806 // Launch the process with a login shell so that users environment
807 // settings get sourced. This does not always happen when MacVim is
809 taskArgs = [NSArray arrayWithObjects:@"-l", @"-c", execArg, nil];
810 taskPath = [[[NSProcessInfo processInfo] environment]
811 objectForKey:@"SHELL"];
813 taskPath = @"/bin/sh";
815 // Run process directly:
817 // (-g for GUI, -f for foreground, i.e. don't fork)
819 taskArgs = [NSArray arrayWithObjects:@"-g", @"-f", nil];
821 taskArgs = [taskArgs arrayByAddingObjectsFromArray:args];
824 NSTask *task =[NSTask launchedTaskWithLaunchPath:taskPath
826 //NSLog(@"launch %@ with args=%@ (pid=%d)", taskPath, taskArgs,
827 // [task processIdentifier]);
829 int pid = [task processIdentifier];
831 // If the process has no arguments, then add a null argument to the
832 // pidArguments dictionary. This is later used to detect that a process
833 // without arguments is being launched.
835 [pidArguments setObject:[NSNull null]
836 forKey:[NSNumber numberWithInt:pid]];
841 - (NSArray *)filterFilesAndNotify:(NSArray *)filenames
843 // Go trough 'filenames' array and make sure each file exists. Present
844 // warning dialog if some file was missing.
846 NSString *firstMissingFile = nil;
847 NSMutableArray *files = [NSMutableArray array];
848 unsigned i, count = [filenames count];
850 for (i = 0; i < count; ++i) {
851 NSString *name = [filenames objectAtIndex:i];
852 if ([[NSFileManager defaultManager] fileExistsAtPath:name]) {
853 [files addObject:name];
854 } else if (!firstMissingFile) {
855 firstMissingFile = name;
859 if (firstMissingFile) {
860 NSAlert *alert = [[NSAlert alloc] init];
861 [alert addButtonWithTitle:@"OK"];
864 if ([files count] >= count-1) {
865 [alert setMessageText:@"File not found"];
866 text = [NSString stringWithFormat:@"Could not open file with "
867 "name %@.", firstMissingFile];
869 [alert setMessageText:@"Multiple files not found"];
870 text = [NSString stringWithFormat:@"Could not open file with "
871 "name %@, and %d other files.", firstMissingFile,
872 count-[files count]-1];
875 [alert setInformativeText:text];
876 [alert setAlertStyle:NSWarningAlertStyle];
881 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
887 - (NSArray *)filterOpenFiles:(NSArray *)filenames remote:(OSType)theID
888 path:(NSString *)path
889 token:(NSAppleEventDescriptor *)token
890 selectionRange:(NSRange)selectionRange
892 // Check if any of the files in the 'filenames' array are open in any Vim
893 // process. Remove the files that are open from the 'filenames' array and
894 // return it. If all files were filtered out, then raise the first file in
895 // the Vim process it is open. Files that are filtered are sent an odb
896 // open event in case theID is not zero.
898 MMVimController *raiseController = nil;
899 NSString *raiseFile = nil;
900 NSMutableArray *files = [filenames mutableCopy];
901 NSString *expr = [NSString stringWithFormat:
902 @"map([\"%@\"],\"bufloaded(v:val)\")",
903 [files componentsJoinedByString:@"\",\""]];
904 unsigned i, count = [vimControllers count];
906 for (i = 0; i < count && [files count]; ++i) {
907 MMVimController *controller = [vimControllers objectAtIndex:i];
908 id proxy = [controller backendProxy];
911 NSString *eval = [proxy evaluateExpression:expr];
912 NSIndexSet *idxSet = [NSIndexSet indexSetWithVimList:eval];
913 if ([idxSet count]) {
915 // Remember the file and which Vim that has it open so that
916 // we can raise it later on.
917 raiseController = controller;
918 raiseFile = [files objectAtIndex:[idxSet firstIndex]];
919 [[raiseFile retain] autorelease];
922 // Send an odb open event to the Vim process.
924 [controller odbEdit:[files objectsAtIndexes:idxSet]
925 server:theID path:path token:token];
927 // Remove all the files that were open in this Vim process and
928 // create a new expression to evaluate.
929 [files removeObjectsAtIndexes:idxSet];
930 expr = [NSString stringWithFormat:
931 @"map([\"%@\"],\"bufloaded(v:val)\")",
932 [files componentsJoinedByString:@"\",\""]];
935 @catch (NSException *e) {
940 if (![files count] && raiseFile) {
941 // Raise the window containing the first file that was already open,
942 // and make sure that the tab containing that file is selected. Only
943 // do this if there are no more files to open, otherwise sometimes the
944 // window with 'raiseFile' will be raised, other times it might be the
945 // window that will open with the files in the 'files' array.
946 raiseFile = [raiseFile stringByEscapingSpecialFilenameCharacters];
947 NSString *input = [NSString stringWithFormat:@"<C-\\><C-N>"
948 ":let oldswb=&swb|let &swb=\"useopen,usetab\"|"
949 "tab sb %@|let &swb=oldswb|unl oldswb|"
950 "cal foreground()|redr|f<CR>", raiseFile];
952 if (selectionRange.location != NSNotFound)
953 input = [input stringByAppendingString:
954 buildSelectRangeCommand(selectionRange)];
956 [raiseController addVimInput:input];
962 #if MM_HANDLE_XCODE_MOD_EVENT
963 - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
964 replyEvent:(NSAppleEventDescriptor *)reply
967 // Xcode sends this event to query MacVim which open files have been
969 NSLog(@"reply:%@", reply);
970 NSLog(@"event:%@", event);
972 NSEnumerator *e = [vimControllers objectEnumerator];
974 while ((vc = [e nextObject])) {
975 DescType type = [reply descriptorType];
976 unsigned len = [[type data] length];
977 NSMutableData *data = [NSMutableData data];
979 [data appendBytes:&type length:sizeof(DescType)];
980 [data appendBytes:&len length:sizeof(unsigned)];
981 [data appendBytes:[reply data] length:len];
983 [vc sendMessage:XcodeModMsgID data:data];
989 - (int)findLaunchingProcessWithoutArguments
991 NSArray *keys = [pidArguments allKeysForObject:[NSNull null]];
992 if ([keys count] > 0) {
993 //NSLog(@"found launching process without arguments");
994 return [[keys objectAtIndex:0] intValue];
1000 - (MMVimController *)findUntitledWindow
1002 NSEnumerator *e = [vimControllers objectEnumerator];
1004 while ((vc = [e nextObject])) {
1005 // TODO: This is a moronic test...should query the Vim process if there
1006 // are any open buffers or something like that instead.
1007 NSString *title = [[[vc windowController] window] title];
1008 if ([title hasPrefix:@"[No Name]"]) {
1009 //NSLog(@"found untitled window");
1017 @end // MMAppController (Private)
1022 @implementation NSMenu (MMExtras)
1024 - (void)recurseSetAutoenablesItems:(BOOL)on
1026 [self setAutoenablesItems:on];
1028 int i, count = [self numberOfItems];
1029 for (i = 0; i < count; ++i) {
1030 NSMenuItem *item = [self itemAtIndex:i];
1031 [item setEnabled:YES];
1032 NSMenu *submenu = [item submenu];
1034 [submenu recurseSetAutoenablesItems:on];
1039 @end // NSMenu (MMExtras)
1044 @implementation NSNumber (MMExtras)
1047 return [self intValue];
1049 @end // NSNumber (MMExtras)