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],
115 [NSNumber numberWithBool:NO], MMTexturedWindowKey,
118 [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
120 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
121 [NSApp registerServicesMenuSendTypes:types returnTypes:types];
126 if ((self = [super init])) {
127 fontContainerRef = loadFonts();
129 vimControllers = [NSMutableArray new];
130 pidArguments = [NSMutableDictionary new];
132 // NOTE! If the name of the connection changes here it must also be
133 // updated in MMBackend.m.
134 NSConnection *connection = [NSConnection defaultConnection];
135 NSString *name = [NSString stringWithFormat:@"%@-connection",
136 [[NSBundle mainBundle] bundleIdentifier]];
137 //NSLog(@"Registering connection with name '%@'", name);
138 if ([connection registerName:name]) {
139 [connection setRequestTimeout:MMRequestTimeout];
140 [connection setReplyTimeout:MMReplyTimeout];
141 [connection setRootObject:self];
143 // NOTE: When the user is resizing the window the AppKit puts the
144 // run loop in event tracking mode. Unless the connection listens
145 // to request in this mode, live resizing won't work.
146 [connection addRequestMode:NSEventTrackingRunLoopMode];
148 NSLog(@"WARNING: Failed to register connection with name '%@'",
158 //NSLog(@"MMAppController dealloc");
160 [pidArguments release]; pidArguments = nil;
161 [vimControllers release]; vimControllers = nil;
162 [openSelectionString release]; openSelectionString = nil;
167 #if MM_HANDLE_XCODE_MOD_EVENT
168 - (void)applicationWillFinishLaunching:(NSNotification *)notification
170 [[NSAppleEventManager sharedAppleEventManager]
172 andSelector:@selector(handleXcodeModEvent:replyEvent:)
178 - (void)applicationDidFinishLaunching:(NSNotification *)notification
180 [NSApp setServicesProvider:self];
183 - (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
185 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
186 NSAppleEventManager *aem = [NSAppleEventManager sharedAppleEventManager];
187 NSAppleEventDescriptor *desc = [aem currentAppleEvent];
189 // The user default MMUntitledWindow can be set to control whether an
190 // untitled window should open on 'Open' and 'Reopen' events.
191 int untitledWindowFlag = [ud integerForKey:MMUntitledWindowKey];
192 if ([desc eventID] == kAEOpenApplication
193 && (untitledWindowFlag & MMUntitledWindowOnOpen) == 0)
195 else if ([desc eventID] == kAEReopenApplication
196 && (untitledWindowFlag & MMUntitledWindowOnReopen) == 0)
199 // When a process is started from the command line, the 'Open' event will
200 // contain a parameter to surpress the opening of an untitled window.
201 desc = [desc paramDescriptorForKeyword:keyAEPropData];
202 desc = [desc paramDescriptorForKeyword:keyMMUntitledWindow];
203 if (desc && ![desc booleanValue])
206 // Never open an untitled window if there is at least one open window or if
207 // there are processes that are currently launching.
208 if ([vimControllers count] > 0 || [pidArguments count] > 0)
211 // NOTE! This way it possible to start the app with the command-line
212 // argument '-nowindow yes' and no window will be opened by default.
213 return ![ud boolForKey:MMNoWindowKey];
216 - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
218 [self newWindow:self];
222 - (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
224 // Opening files works like this:
225 // a) extract ODB and/or Xcode parameters from the current Apple event
226 // b) filter out any already open files (see filterOpenFiles:::::)
227 // c) open any remaining files
229 // A file is opened in an untitled window if there is one (it may be
230 // currently launching, or it may already be visible), otherwise a new
233 // Each launching Vim process has a dictionary of arguments that are passed
234 // to the process when in checks in (via connectBackend:pid:). The
235 // arguments for each launching process can be looked up by its PID (in the
236 // pidArguments dictionary).
239 NSString *remotePath = nil;
240 NSAppleEventManager *aem = nil;
241 NSAppleEventDescriptor *remoteToken = nil;
242 NSAppleEventDescriptor *odbdesc = nil;
243 NSAppleEventDescriptor *xcodedesc = nil;
244 NSRange selectionRange = { NSNotFound, 0 };
246 // 1. Extract ODB parameters (if any)
247 aem = [NSAppleEventManager sharedAppleEventManager];
248 odbdesc = [aem currentAppleEvent];
249 if (![odbdesc paramDescriptorForKeyword:keyFileSender]) {
250 // The ODB paramaters may hide inside the 'keyAEPropData' descriptor.
251 odbdesc = [odbdesc paramDescriptorForKeyword:keyAEPropData];
252 if (![odbdesc paramDescriptorForKeyword:keyFileSender])
257 remoteID = [[odbdesc paramDescriptorForKeyword:keyFileSender]
259 remotePath = [[odbdesc paramDescriptorForKeyword:keyFileCustomPath]
261 remoteToken = [[odbdesc paramDescriptorForKeyword:keyFileSenderToken]
265 // 2. Extract Xcode parameters (if any)
266 xcodedesc = [[aem currentAppleEvent]
267 paramDescriptorForKeyword:keyAEPosition];
269 MMSelectionRange *sr = (MMSelectionRange*)[[xcodedesc data] bytes];
270 if (sr->lineNum < 0) {
271 // Should select a range of lines.
272 selectionRange.location = sr->startRange + 1;
273 selectionRange.length = sr->endRange - sr->startRange + 1;
275 // Should only move cursor to a line.
276 selectionRange.location = sr->lineNum + 1;
280 // 3. Filter out files that are already open
281 filenames = [self filterOpenFiles:filenames remote:remoteID path:remotePath
283 selectionRange:selectionRange];
285 // 4. Open any files that remain
286 if ([filenames count]) {
288 BOOL openInTabs = [[NSUserDefaults standardUserDefaults]
289 boolForKey:MMOpenFilesInTabsKey];
291 if ((openInTabs && (vc = [self topmostVimController]))
292 || (vc = [self findUntitledWindow])) {
294 // 5-1 Open files in an already open window (either due to the fact
295 // that MMMOpenFilesInTabs was set or because there was already an
296 // untitled Vim process open).
298 [vc dropFiles:filenames forceOpen:YES];
300 [vc odbEdit:filenames server:remoteID path:remotePath
302 if (selectionRange.location != NSNotFound)
303 [vc addVimInput:buildSelectRangeCommand(selectionRange)];
307 // a) in a launching Vim that has no arguments, if there is one
309 // b) launch a new Vim process and open files there.
311 NSMutableDictionary *argDict = [NSMutableDictionary dictionary];
313 int pid = [self findLaunchingProcessWithoutArguments];
315 // The filenames are passed as arguments in connectBackend::.
316 [argDict setObject:filenames forKey:@"filenames"];
318 // Pass the filenames to the process straight away.
320 // TODO: It would be nicer if all arguments were passed to the
321 // Vim process in connectBackend::, but if we don't pass the
322 // filename arguments here, the window 'flashes' once when it
323 // opens. This is due to the 'welcome' screen first being
324 // displayed, then quickly thereafter the files are opened.
325 NSArray *fileArgs = [NSArray arrayWithObject:@"-p"];
326 fileArgs = [fileArgs arrayByAddingObjectsFromArray:filenames];
328 pid = [self launchVimProcessWithArguments:fileArgs];
331 // TODO: If the Vim process fails to start, or if it changes PID,
332 // then the memory allocated for these parameters will leak.
333 // Ensure that this cannot happen or somehow detect it.
336 [argDict setObject:filenames forKey:@"remoteFiles"];
338 // The remote token can be arbitrary data so it is cannot
339 // (without encoding it as text) be passed on the command line.
340 [argDict setObject:[NSNumber numberWithUnsignedInt:remoteID]
343 [argDict setObject:remotePath forKey:@"remotePath"];
345 [argDict setObject:remoteToken forKey:@"remoteToken"];
348 if (selectionRange.location != NSNotFound)
349 [argDict setObject:NSStringFromRange(selectionRange)
350 forKey:@"selectionRange"];
352 if ([argDict count] > 0)
353 [pidArguments setObject:argDict
354 forKey:[NSNumber numberWithInt:pid]];
358 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
359 // NSApplicationDelegateReplySuccess = 0,
360 // NSApplicationDelegateReplyCancel = 1,
361 // NSApplicationDelegateReplyFailure = 2
364 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
366 return [[NSUserDefaults standardUserDefaults]
367 boolForKey:MMTerminateAfterLastWindowClosedKey];
370 - (NSApplicationTerminateReply)applicationShouldTerminate:
371 (NSApplication *)sender
373 // TODO: Follow Apple's guidelines for 'Graceful Application Termination'
374 // (in particular, allow user to review changes and save).
375 int reply = NSTerminateNow;
376 BOOL modifiedBuffers = NO;
378 // Go through windows, checking for modified buffers. (Each Vim process
379 // tells MacVim when any buffer has been modified and MacVim sets the
380 // 'documentEdited' flag of the window correspondingly.)
381 NSEnumerator *e = [[NSApp windows] objectEnumerator];
383 while ((window = [e nextObject])) {
384 if ([window isDocumentEdited]) {
385 modifiedBuffers = YES;
390 if (modifiedBuffers) {
391 NSAlert *alert = [[NSAlert alloc] init];
392 [alert addButtonWithTitle:@"Quit"];
393 [alert addButtonWithTitle:@"Cancel"];
394 [alert setMessageText:@"Quit without saving?"];
395 [alert setInformativeText:@"There are modified buffers, "
396 "if you quit now all changes will be lost. Quit anyway?"];
397 [alert setAlertStyle:NSWarningAlertStyle];
399 if ([alert runModal] != NSAlertFirstButtonReturn)
400 reply = NSTerminateCancel;
405 // Tell all Vim processes to terminate now (otherwise they'll leave swap
407 if (NSTerminateNow == reply) {
408 e = [vimControllers objectEnumerator];
410 while ((vc = [e nextObject]))
411 [vc sendMessage:TerminateNowMsgID data:nil];
417 - (void)applicationWillTerminate:(NSNotification *)notification
419 #if MM_HANDLE_XCODE_MOD_EVENT
420 [[NSAppleEventManager sharedAppleEventManager]
421 removeEventHandlerForEventClass:'KAHL'
425 // This will invalidate all connections (since they were spawned from the
426 // default connection).
427 [[NSConnection defaultConnection] invalidate];
429 // Send a SIGINT to all running Vim processes, so that they are sure to
430 // receive the connectionDidDie: notification (a process has to be checking
431 // the run-loop for this to happen).
432 unsigned i, count = [vimControllers count];
433 for (i = 0; i < count; ++i) {
434 MMVimController *controller = [vimControllers objectAtIndex:i];
435 int pid = [controller pid];
440 if (fontContainerRef) {
441 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
442 fontContainerRef = 0;
445 // TODO: Is this a correct way of releasing the MMAppController?
446 // (It doesn't seem like dealloc is ever called.)
447 [NSApp setDelegate:nil];
451 - (void)removeVimController:(id)controller
453 //NSLog(@"%s%@", _cmd, controller);
455 [[controller windowController] close];
457 [vimControllers removeObject:controller];
459 if (![vimControllers count]) {
460 // Turn on autoenabling of menus (because no Vim is open to handle it),
461 // but do not touch the MacVim menu. Note that the menus must be
462 // enabled first otherwise autoenabling does not work.
463 NSMenu *mainMenu = [NSApp mainMenu];
464 int i, count = [mainMenu numberOfItems];
465 for (i = 1; i < count; ++i) {
466 NSMenuItem *item = [mainMenu itemAtIndex:i];
467 [item setEnabled:YES];
468 [[item submenu] recurseSetAutoenablesItems:YES];
473 - (void)windowControllerWillOpen:(MMWindowController *)windowController
475 NSPoint topLeft = NSZeroPoint;
476 NSWindow *keyWin = [NSApp keyWindow];
477 NSWindow *win = [windowController window];
481 // If there is a key window, cascade from it, otherwise use the autosaved
482 // window position (if any).
484 NSRect frame = [keyWin frame];
485 topLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
487 NSString *topLeftString = [[NSUserDefaults standardUserDefaults]
488 stringForKey:MMTopLeftPointKey];
490 topLeft = NSPointFromString(topLeftString);
493 if (!NSEqualPoints(topLeft, NSZeroPoint)) {
495 topLeft = [win cascadeTopLeftFromPoint:topLeft];
497 [win setFrameTopLeftPoint:topLeft];
500 if (openSelectionString) {
501 // TODO: Pass this as a parameter instead! Get rid of
502 // 'openSelectionString' etc.
504 // There is some text to paste into this window as a result of the
505 // services menu "Open selection ..." being used.
506 [[windowController vimController] dropString:openSelectionString];
507 [openSelectionString release];
508 openSelectionString = nil;
512 - (IBAction)newWindow:(id)sender
514 [self launchVimProcessWithArguments:nil];
517 - (IBAction)fileOpen:(id)sender
519 NSOpenPanel *panel = [NSOpenPanel openPanel];
520 [panel setAllowsMultipleSelection:YES];
522 int result = [panel runModalForTypes:nil];
523 if (NSOKButton == result)
524 [self application:NSApp openFiles:[panel filenames]];
527 - (IBAction)selectNextWindow:(id)sender
529 unsigned i, count = [vimControllers count];
532 NSWindow *keyWindow = [NSApp keyWindow];
533 for (i = 0; i < count; ++i) {
534 MMVimController *vc = [vimControllers objectAtIndex:i];
535 if ([[[vc windowController] window] isEqual:keyWindow])
542 MMVimController *vc = [vimControllers objectAtIndex:i];
543 [[vc windowController] showWindow:self];
547 - (IBAction)selectPreviousWindow:(id)sender
549 unsigned i, count = [vimControllers count];
552 NSWindow *keyWindow = [NSApp keyWindow];
553 for (i = 0; i < count; ++i) {
554 MMVimController *vc = [vimControllers objectAtIndex:i];
555 if ([[[vc windowController] window] isEqual:keyWindow])
565 MMVimController *vc = [vimControllers objectAtIndex:i];
566 [[vc windowController] showWindow:self];
570 - (IBAction)fontSizeUp:(id)sender
572 [[NSFontManager sharedFontManager] modifyFont:
573 [NSNumber numberWithInt:NSSizeUpFontAction]];
576 - (IBAction)fontSizeDown:(id)sender
578 [[NSFontManager sharedFontManager] modifyFont:
579 [NSNumber numberWithInt:NSSizeDownFontAction]];
582 - (byref id <MMFrontendProtocol>)
583 connectBackend:(byref in id <MMBackendProtocol>)backend
586 //NSLog(@"Connect backend (pid=%d)", pid);
587 NSNumber *pidKey = [NSNumber numberWithInt:pid];
588 MMVimController *vc = nil;
591 [(NSDistantObject*)backend
592 setProtocolForProxy:@protocol(MMBackendProtocol)];
594 vc = [[[MMVimController alloc]
595 initWithBackend:backend pid:pid] autorelease];
597 if (![vimControllers count]) {
598 // The first window autosaves its position. (The autosaving
599 // features of Cocoa are not used because we need more control over
600 // what is autosaved and when it is restored.)
601 [[vc windowController] setWindowAutosaveKey:MMTopLeftPointKey];
604 [vimControllers addObject:vc];
606 // Pass arguments to the Vim process.
607 id args = [pidArguments objectForKey:pidKey];
608 if (args && args != [NSNull null]) {
609 // Pass filenames to open
610 NSArray *filenames = [args objectForKey:@"filenames"];
612 NSString *tabDrop = buildTabDropCommand(filenames);
613 [vc addVimInput:tabDrop];
617 if ([args objectForKey:@"remoteFiles"]
618 && [args objectForKey:@"remoteID"]) {
619 [vc odbEdit:[args objectForKey:@"remoteFiles"]
620 server:[[args objectForKey:@"remoteID"] unsignedIntValue]
621 path:[args objectForKey:@"remotePath"]
622 token:[args objectForKey:@"remoteToken"]];
625 // Pass range of lines to select
626 if ([args objectForKey:@"selectionRange"]) {
627 NSRange selectionRange = NSRangeFromString(
628 [args objectForKey:@"selectionRange"]);
629 [vc addVimInput:buildSelectRangeCommand(selectionRange)];
633 // HACK! MacVim does not get activated if it is launched from the
634 // terminal, so we forcibly activate here unless it is an untitled
635 // window opening. Untitled windows are treated differently, else
636 // MacVim would steal the focus if another app was activated while the
637 // untitled window was loading.
638 if (!args || args != [NSNull null])
639 [NSApp activateIgnoringOtherApps:YES];
642 [pidArguments removeObjectForKey:pidKey];
647 @catch (NSException *e) {
648 NSLog(@"Exception caught in %s: \"%@\"", _cmd, e);
651 [vimControllers removeObject:vc];
653 [pidArguments removeObjectForKey:pidKey];
659 - (NSArray *)serverList
661 NSMutableArray *array = [NSMutableArray array];
663 unsigned i, count = [vimControllers count];
664 for (i = 0; i < count; ++i) {
665 MMVimController *controller = [vimControllers objectAtIndex:i];
666 if ([controller serverName])
667 [array addObject:[controller serverName]];
673 @end // MMAppController
678 @implementation MMAppController (MMServices)
680 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
681 error:(NSString **)error
683 if (![[pboard types] containsObject:NSStringPboardType]) {
684 NSLog(@"WARNING: Pasteboard contains no object of type "
685 "NSStringPboardType");
689 MMVimController *vc = [self topmostVimController];
691 // Open a new tab first, since dropString: does not do this.
692 [vc sendMessage:AddNewTabMsgID data:nil];
693 [vc dropString:[pboard stringForType:NSStringPboardType]];
695 // NOTE: There is no window to paste the selection into, so save the
696 // text, open a new window, and paste the text when the next window
697 // opens. (If this is called several times in a row, then all but the
698 // last call might be ignored.)
699 if (openSelectionString) [openSelectionString release];
700 openSelectionString = [[pboard stringForType:NSStringPboardType] copy];
702 [self newWindow:self];
706 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
707 error:(NSString **)error
709 if (![[pboard types] containsObject:NSStringPboardType]) {
710 NSLog(@"WARNING: Pasteboard contains no object of type "
711 "NSStringPboardType");
715 // TODO: Parse multiple filenames and create array with names.
716 NSString *string = [pboard stringForType:NSStringPboardType];
717 string = [string stringByTrimmingCharactersInSet:
718 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
719 string = [string stringByStandardizingPath];
721 NSArray *filenames = [self filterFilesAndNotify:
722 [NSArray arrayWithObject:string]];
723 if ([filenames count] > 0) {
724 MMVimController *vc = nil;
725 if (userData && [userData isEqual:@"Tab"])
726 vc = [self topmostVimController];
729 [vc dropFiles:filenames forceOpen:YES];
731 [self application:NSApp openFiles:filenames];
736 @end // MMAppController (MMServices)
741 @implementation MMAppController (Private)
743 - (MMVimController *)keyVimController
745 NSWindow *keyWindow = [NSApp keyWindow];
747 unsigned i, count = [vimControllers count];
748 for (i = 0; i < count; ++i) {
749 MMVimController *vc = [vimControllers objectAtIndex:i];
750 if ([[[vc windowController] window] isEqual:keyWindow])
758 - (MMVimController *)topmostVimController
760 NSArray *windows = [NSApp orderedWindows];
761 if ([windows count] > 0) {
762 NSWindow *window = [windows objectAtIndex:0];
763 unsigned i, count = [vimControllers count];
764 for (i = 0; i < count; ++i) {
765 MMVimController *vc = [vimControllers objectAtIndex:i];
766 if ([[[vc windowController] window] isEqual:window])
774 - (int)launchVimProcessWithArguments:(NSArray *)args
776 NSString *taskPath = nil;
777 NSArray *taskArgs = nil;
778 NSString *path = [[NSBundle mainBundle] pathForAuxiliaryExecutable:@"Vim"];
781 NSLog(@"ERROR: Vim executable could not be found inside app bundle!");
785 if ([[NSUserDefaults standardUserDefaults] boolForKey:MMLoginShellKey]) {
786 // Run process with a login shell
787 // $SHELL -l -c "exec Vim -g -f args"
788 // (-g for GUI, -f for foreground, i.e. don't fork)
790 NSMutableString *execArg = [NSMutableString
791 stringWithFormat:@"exec \"%@\" -g -f", path];
793 // Append all arguments while making sure that arguments containing
794 // spaces are enclosed in quotes.
795 NSCharacterSet *space = [NSCharacterSet whitespaceCharacterSet];
796 unsigned i, count = [args count];
798 for (i = 0; i < count; ++i) {
799 NSString *arg = [args objectAtIndex:i];
800 if (NSNotFound != [arg rangeOfCharacterFromSet:space].location)
801 [execArg appendFormat:@" \"%@\"", arg];
803 [execArg appendFormat:@" %@", arg];
807 // Launch the process with a login shell so that users environment
808 // settings get sourced. This does not always happen when MacVim is
810 taskArgs = [NSArray arrayWithObjects:@"-l", @"-c", execArg, nil];
811 taskPath = [[[NSProcessInfo processInfo] environment]
812 objectForKey:@"SHELL"];
814 taskPath = @"/bin/sh";
816 // Run process directly:
818 // (-g for GUI, -f for foreground, i.e. don't fork)
820 taskArgs = [NSArray arrayWithObjects:@"-g", @"-f", nil];
822 taskArgs = [taskArgs arrayByAddingObjectsFromArray:args];
825 NSTask *task =[NSTask launchedTaskWithLaunchPath:taskPath
827 //NSLog(@"launch %@ with args=%@ (pid=%d)", taskPath, taskArgs,
828 // [task processIdentifier]);
830 int pid = [task processIdentifier];
832 // If the process has no arguments, then add a null argument to the
833 // pidArguments dictionary. This is later used to detect that a process
834 // without arguments is being launched.
836 [pidArguments setObject:[NSNull null]
837 forKey:[NSNumber numberWithInt:pid]];
842 - (NSArray *)filterFilesAndNotify:(NSArray *)filenames
844 // Go trough 'filenames' array and make sure each file exists. Present
845 // warning dialog if some file was missing.
847 NSString *firstMissingFile = nil;
848 NSMutableArray *files = [NSMutableArray array];
849 unsigned i, count = [filenames count];
851 for (i = 0; i < count; ++i) {
852 NSString *name = [filenames objectAtIndex:i];
853 if ([[NSFileManager defaultManager] fileExistsAtPath:name]) {
854 [files addObject:name];
855 } else if (!firstMissingFile) {
856 firstMissingFile = name;
860 if (firstMissingFile) {
861 NSAlert *alert = [[NSAlert alloc] init];
862 [alert addButtonWithTitle:@"OK"];
865 if ([files count] >= count-1) {
866 [alert setMessageText:@"File not found"];
867 text = [NSString stringWithFormat:@"Could not open file with "
868 "name %@.", firstMissingFile];
870 [alert setMessageText:@"Multiple files not found"];
871 text = [NSString stringWithFormat:@"Could not open file with "
872 "name %@, and %d other files.", firstMissingFile,
873 count-[files count]-1];
876 [alert setInformativeText:text];
877 [alert setAlertStyle:NSWarningAlertStyle];
882 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
888 - (NSArray *)filterOpenFiles:(NSArray *)filenames remote:(OSType)theID
889 path:(NSString *)path
890 token:(NSAppleEventDescriptor *)token
891 selectionRange:(NSRange)selectionRange
893 // Check if any of the files in the 'filenames' array are open in any Vim
894 // process. Remove the files that are open from the 'filenames' array and
895 // return it. If all files were filtered out, then raise the first file in
896 // the Vim process it is open. Files that are filtered are sent an odb
897 // open event in case theID is not zero.
899 MMVimController *raiseController = nil;
900 NSString *raiseFile = nil;
901 NSMutableArray *files = [filenames mutableCopy];
902 NSString *expr = [NSString stringWithFormat:
903 @"map([\"%@\"],\"bufloaded(v:val)\")",
904 [files componentsJoinedByString:@"\",\""]];
905 unsigned i, count = [vimControllers count];
907 for (i = 0; i < count && [files count]; ++i) {
908 MMVimController *controller = [vimControllers objectAtIndex:i];
909 id proxy = [controller backendProxy];
912 NSString *eval = [proxy evaluateExpression:expr];
913 NSIndexSet *idxSet = [NSIndexSet indexSetWithVimList:eval];
914 if ([idxSet count]) {
916 // Remember the file and which Vim that has it open so that
917 // we can raise it later on.
918 raiseController = controller;
919 raiseFile = [files objectAtIndex:[idxSet firstIndex]];
920 [[raiseFile retain] autorelease];
923 // Send an odb open event to the Vim process.
925 [controller odbEdit:[files objectsAtIndexes:idxSet]
926 server:theID path:path token:token];
928 // Remove all the files that were open in this Vim process and
929 // create a new expression to evaluate.
930 [files removeObjectsAtIndexes:idxSet];
931 expr = [NSString stringWithFormat:
932 @"map([\"%@\"],\"bufloaded(v:val)\")",
933 [files componentsJoinedByString:@"\",\""]];
936 @catch (NSException *e) {
941 if (![files count] && raiseFile) {
942 // Raise the window containing the first file that was already open,
943 // and make sure that the tab containing that file is selected. Only
944 // do this if there are no more files to open, otherwise sometimes the
945 // window with 'raiseFile' will be raised, other times it might be the
946 // window that will open with the files in the 'files' array.
947 raiseFile = [raiseFile stringByEscapingSpecialFilenameCharacters];
948 NSString *input = [NSString stringWithFormat:@"<C-\\><C-N>"
949 ":let oldswb=&swb|let &swb=\"useopen,usetab\"|"
950 "tab sb %@|let &swb=oldswb|unl oldswb|"
951 "cal foreground()|redr|f<CR>", raiseFile];
953 if (selectionRange.location != NSNotFound)
954 input = [input stringByAppendingString:
955 buildSelectRangeCommand(selectionRange)];
957 [raiseController addVimInput:input];
963 #if MM_HANDLE_XCODE_MOD_EVENT
964 - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
965 replyEvent:(NSAppleEventDescriptor *)reply
968 // Xcode sends this event to query MacVim which open files have been
970 NSLog(@"reply:%@", reply);
971 NSLog(@"event:%@", event);
973 NSEnumerator *e = [vimControllers objectEnumerator];
975 while ((vc = [e nextObject])) {
976 DescType type = [reply descriptorType];
977 unsigned len = [[type data] length];
978 NSMutableData *data = [NSMutableData data];
980 [data appendBytes:&type length:sizeof(DescType)];
981 [data appendBytes:&len length:sizeof(unsigned)];
982 [data appendBytes:[reply data] length:len];
984 [vc sendMessage:XcodeModMsgID data:data];
990 - (int)findLaunchingProcessWithoutArguments
992 NSArray *keys = [pidArguments allKeysForObject:[NSNull null]];
993 if ([keys count] > 0) {
994 //NSLog(@"found launching process without arguments");
995 return [[keys objectAtIndex:0] intValue];
1001 - (MMVimController *)findUntitledWindow
1003 NSEnumerator *e = [vimControllers objectEnumerator];
1005 while ((vc = [e nextObject])) {
1006 // TODO: This is a moronic test...should query the Vim process if there
1007 // are any open buffers or something like that instead.
1008 NSString *title = [[[vc windowController] window] title];
1009 if ([title hasPrefix:@"[No Name]"]) {
1010 //NSLog(@"found untitled window");
1018 @end // MMAppController (Private)
1023 @implementation NSMenu (MMExtras)
1025 - (void)recurseSetAutoenablesItems:(BOOL)on
1027 [self setAutoenablesItems:on];
1029 int i, count = [self numberOfItems];
1030 for (i = 0; i < count; ++i) {
1031 NSMenuItem *item = [self itemAtIndex:i];
1032 [item setEnabled:YES];
1033 NSMenu *submenu = [item submenu];
1035 [submenu recurseSetAutoenablesItems:on];
1040 @end // NSMenu (MMExtras)
1045 @implementation NSNumber (MMExtras)
1048 return [self intValue];
1050 @end // NSNumber (MMExtras)