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
70 arguments:(NSDictionary *)args;
71 #if MM_HANDLE_XCODE_MOD_EVENT
72 - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
73 replyEvent:(NSAppleEventDescriptor *)reply;
75 - (int)findLaunchingProcessWithoutArguments;
76 - (MMVimController *)findUntitledWindow;
77 - (NSMutableDictionary *)extractArgumentsFromOdocEvent:
78 (NSAppleEventDescriptor *)desc;
79 - (void)passArguments:(NSDictionary *)args toVimController:(MMVimController*)vc;
82 @interface NSMenu (MMExtras)
83 - (void)recurseSetAutoenablesItems:(BOOL)on;
86 @interface NSNumber (MMExtras)
92 @implementation MMAppController
96 NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
97 [NSNumber numberWithBool:NO], MMNoWindowKey,
98 [NSNumber numberWithInt:64], MMTabMinWidthKey,
99 [NSNumber numberWithInt:6*64], MMTabMaxWidthKey,
100 [NSNumber numberWithInt:132], MMTabOptimumWidthKey,
101 [NSNumber numberWithInt:2], MMTextInsetLeftKey,
102 [NSNumber numberWithInt:1], MMTextInsetRightKey,
103 [NSNumber numberWithInt:1], MMTextInsetTopKey,
104 [NSNumber numberWithInt:1], MMTextInsetBottomKey,
105 [NSNumber numberWithBool:NO], MMTerminateAfterLastWindowClosedKey,
106 @"MMTypesetter", MMTypesetterKey,
107 [NSNumber numberWithFloat:1], MMCellWidthMultiplierKey,
108 [NSNumber numberWithFloat:-1], MMBaselineOffsetKey,
109 [NSNumber numberWithBool:YES], MMTranslateCtrlClickKey,
110 [NSNumber numberWithBool:NO], MMOpenFilesInTabsKey,
111 [NSNumber numberWithBool:NO], MMNoFontSubstitutionKey,
112 [NSNumber numberWithBool:NO], MMLoginShellKey,
113 [NSNumber numberWithBool:NO], MMAtsuiRendererKey,
114 [NSNumber numberWithInt:MMUntitledWindowAlways],
116 [NSNumber numberWithBool:NO], MMTexturedWindowKey,
119 [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
121 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
122 [NSApp registerServicesMenuSendTypes:types returnTypes:types];
127 if ((self = [super init])) {
128 fontContainerRef = loadFonts();
130 vimControllers = [NSMutableArray new];
131 pidArguments = [NSMutableDictionary new];
133 // NOTE! If the name of the connection changes here it must also be
134 // updated in MMBackend.m.
135 NSConnection *connection = [NSConnection defaultConnection];
136 NSString *name = [NSString stringWithFormat:@"%@-connection",
137 [[NSBundle mainBundle] bundleIdentifier]];
138 //NSLog(@"Registering connection with name '%@'", name);
139 if ([connection registerName:name]) {
140 [connection setRequestTimeout:MMRequestTimeout];
141 [connection setReplyTimeout:MMReplyTimeout];
142 [connection setRootObject:self];
144 // NOTE: When the user is resizing the window the AppKit puts the
145 // run loop in event tracking mode. Unless the connection listens
146 // to request in this mode, live resizing won't work.
147 [connection addRequestMode:NSEventTrackingRunLoopMode];
149 NSLog(@"WARNING: Failed to register connection with name '%@'",
159 //NSLog(@"MMAppController dealloc");
161 [pidArguments release]; pidArguments = nil;
162 [vimControllers release]; vimControllers = nil;
163 [openSelectionString release]; openSelectionString = nil;
168 #if MM_HANDLE_XCODE_MOD_EVENT
169 - (void)applicationWillFinishLaunching:(NSNotification *)notification
171 [[NSAppleEventManager sharedAppleEventManager]
173 andSelector:@selector(handleXcodeModEvent:replyEvent:)
179 - (void)applicationDidFinishLaunching:(NSNotification *)notification
181 [NSApp setServicesProvider:self];
184 - (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
186 NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
187 NSAppleEventManager *aem = [NSAppleEventManager sharedAppleEventManager];
188 NSAppleEventDescriptor *desc = [aem currentAppleEvent];
190 // The user default MMUntitledWindow can be set to control whether an
191 // untitled window should open on 'Open' and 'Reopen' events.
192 int untitledWindowFlag = [ud integerForKey:MMUntitledWindowKey];
193 if ([desc eventID] == kAEOpenApplication
194 && (untitledWindowFlag & MMUntitledWindowOnOpen) == 0)
196 else if ([desc eventID] == kAEReopenApplication
197 && (untitledWindowFlag & MMUntitledWindowOnReopen) == 0)
200 // When a process is started from the command line, the 'Open' event will
201 // contain a parameter to surpress the opening of an untitled window.
202 desc = [desc paramDescriptorForKeyword:keyAEPropData];
203 desc = [desc paramDescriptorForKeyword:keyMMUntitledWindow];
204 if (desc && ![desc booleanValue])
207 // Never open an untitled window if there is at least one open window or if
208 // there are processes that are currently launching.
209 if ([vimControllers count] > 0 || [pidArguments count] > 0)
212 // NOTE! This way it possible to start the app with the command-line
213 // argument '-nowindow yes' and no window will be opened by default.
214 return ![ud boolForKey:MMNoWindowKey];
217 - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
219 [self newWindow:self];
223 - (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
225 // Opening files works like this:
226 // a) extract ODB/Xcode/Spotlight parameters from the current Apple event
227 // b) filter out any already open files (see filterOpenFiles::)
228 // c) open any remaining files
230 // A file is opened in an untitled window if there is one (it may be
231 // currently launching, or it may already be visible), otherwise a new
234 // Each launching Vim process has a dictionary of arguments that are passed
235 // to the process when in checks in (via connectBackend:pid:). The
236 // arguments for each launching process can be looked up by its PID (in the
237 // pidArguments dictionary).
239 NSMutableDictionary *arguments = [self extractArgumentsFromOdocEvent:
240 [[NSAppleEventManager sharedAppleEventManager] currentAppleEvent]];
242 // Filter out files that are already open
243 filenames = [self filterOpenFiles:filenames arguments:arguments];
245 // Open any files that remain
246 if ([filenames count]) {
248 BOOL openInTabs = [[NSUserDefaults standardUserDefaults]
249 boolForKey:MMOpenFilesInTabsKey];
251 [arguments setObject:filenames forKey:@"filenames"];
252 [arguments setObject:[NSNumber numberWithBool:YES] forKey:@"openFiles"];
254 if ((openInTabs && (vc = [self topmostVimController]))
255 || (vc = [self findUntitledWindow])) {
256 // Open files in an already open window.
257 [self passArguments:arguments toVimController:vc];
259 // Open files in a launching Vim process or start a new process.
260 int pid = [self findLaunchingProcessWithoutArguments];
262 // Pass the filenames to the process straight away.
264 // TODO: It would be nicer if all arguments were passed to the
265 // Vim process in connectBackend::, but if we don't pass the
266 // filename arguments here, the window 'flashes' once when it
267 // opens. This is due to the 'welcome' screen first being
268 // displayed, then quickly thereafter the files are opened.
269 NSArray *fileArgs = [NSArray arrayWithObject:@"-p"];
270 fileArgs = [fileArgs arrayByAddingObjectsFromArray:filenames];
272 pid = [self launchVimProcessWithArguments:fileArgs];
274 // Make sure these files aren't opened again when
275 // connectBackend:pid: is called.
276 [arguments setObject:[NSNumber numberWithBool:NO]
277 forKey:@"openFiles"];
280 // TODO: If the Vim process fails to start, or if it changes PID,
281 // then the memory allocated for these parameters will leak.
282 // Ensure that this cannot happen or somehow detect it.
284 if ([arguments count] > 0)
285 [pidArguments setObject:arguments
286 forKey:[NSNumber numberWithInt:pid]];
290 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
291 // NSApplicationDelegateReplySuccess = 0,
292 // NSApplicationDelegateReplyCancel = 1,
293 // NSApplicationDelegateReplyFailure = 2
296 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
298 return [[NSUserDefaults standardUserDefaults]
299 boolForKey:MMTerminateAfterLastWindowClosedKey];
302 - (NSApplicationTerminateReply)applicationShouldTerminate:
303 (NSApplication *)sender
305 // TODO: Follow Apple's guidelines for 'Graceful Application Termination'
306 // (in particular, allow user to review changes and save).
307 int reply = NSTerminateNow;
308 BOOL modifiedBuffers = NO;
310 // Go through windows, checking for modified buffers. (Each Vim process
311 // tells MacVim when any buffer has been modified and MacVim sets the
312 // 'documentEdited' flag of the window correspondingly.)
313 NSEnumerator *e = [[NSApp windows] objectEnumerator];
315 while ((window = [e nextObject])) {
316 if ([window isDocumentEdited]) {
317 modifiedBuffers = YES;
322 if (modifiedBuffers) {
323 NSAlert *alert = [[NSAlert alloc] init];
324 [alert addButtonWithTitle:@"Quit"];
325 [alert addButtonWithTitle:@"Cancel"];
326 [alert setMessageText:@"Quit without saving?"];
327 [alert setInformativeText:@"There are modified buffers, "
328 "if you quit now all changes will be lost. Quit anyway?"];
329 [alert setAlertStyle:NSWarningAlertStyle];
331 if ([alert runModal] != NSAlertFirstButtonReturn)
332 reply = NSTerminateCancel;
337 // Tell all Vim processes to terminate now (otherwise they'll leave swap
339 if (NSTerminateNow == reply) {
340 e = [vimControllers objectEnumerator];
342 while ((vc = [e nextObject]))
343 [vc sendMessage:TerminateNowMsgID data:nil];
349 - (void)applicationWillTerminate:(NSNotification *)notification
351 #if MM_HANDLE_XCODE_MOD_EVENT
352 [[NSAppleEventManager sharedAppleEventManager]
353 removeEventHandlerForEventClass:'KAHL'
357 // This will invalidate all connections (since they were spawned from the
358 // default connection).
359 [[NSConnection defaultConnection] invalidate];
361 // Send a SIGINT to all running Vim processes, so that they are sure to
362 // receive the connectionDidDie: notification (a process has to be checking
363 // the run-loop for this to happen).
364 unsigned i, count = [vimControllers count];
365 for (i = 0; i < count; ++i) {
366 MMVimController *controller = [vimControllers objectAtIndex:i];
367 int pid = [controller pid];
372 if (fontContainerRef) {
373 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
374 fontContainerRef = 0;
377 // TODO: Is this a correct way of releasing the MMAppController?
378 // (It doesn't seem like dealloc is ever called.)
379 [NSApp setDelegate:nil];
383 - (void)removeVimController:(id)controller
385 //NSLog(@"%s%@", _cmd, controller);
387 [[controller windowController] close];
389 [vimControllers removeObject:controller];
391 if (![vimControllers count]) {
392 // Turn on autoenabling of menus (because no Vim is open to handle it),
393 // but do not touch the MacVim menu. Note that the menus must be
394 // enabled first otherwise autoenabling does not work.
395 NSMenu *mainMenu = [NSApp mainMenu];
396 int i, count = [mainMenu numberOfItems];
397 for (i = 1; i < count; ++i) {
398 NSMenuItem *item = [mainMenu itemAtIndex:i];
399 [item setEnabled:YES];
400 [[item submenu] recurseSetAutoenablesItems:YES];
405 - (void)windowControllerWillOpen:(MMWindowController *)windowController
407 NSPoint topLeft = NSZeroPoint;
408 NSWindow *keyWin = [NSApp keyWindow];
409 NSWindow *win = [windowController window];
413 // If there is a key window, cascade from it, otherwise use the autosaved
414 // window position (if any).
416 NSRect frame = [keyWin frame];
417 topLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
419 NSString *topLeftString = [[NSUserDefaults standardUserDefaults]
420 stringForKey:MMTopLeftPointKey];
422 topLeft = NSPointFromString(topLeftString);
425 if (!NSEqualPoints(topLeft, NSZeroPoint)) {
427 topLeft = [win cascadeTopLeftFromPoint:topLeft];
429 [win setFrameTopLeftPoint:topLeft];
432 if (openSelectionString) {
433 // TODO: Pass this as a parameter instead! Get rid of
434 // 'openSelectionString' etc.
436 // There is some text to paste into this window as a result of the
437 // services menu "Open selection ..." being used.
438 [[windowController vimController] dropString:openSelectionString];
439 [openSelectionString release];
440 openSelectionString = nil;
444 - (IBAction)newWindow:(id)sender
446 [self launchVimProcessWithArguments:nil];
449 - (IBAction)fileOpen:(id)sender
451 NSOpenPanel *panel = [NSOpenPanel openPanel];
452 [panel setAllowsMultipleSelection:YES];
454 int result = [panel runModalForTypes:nil];
455 if (NSOKButton == result)
456 [self application:NSApp openFiles:[panel filenames]];
459 - (IBAction)selectNextWindow:(id)sender
461 unsigned i, count = [vimControllers count];
464 NSWindow *keyWindow = [NSApp keyWindow];
465 for (i = 0; i < count; ++i) {
466 MMVimController *vc = [vimControllers objectAtIndex:i];
467 if ([[[vc windowController] window] isEqual:keyWindow])
474 MMVimController *vc = [vimControllers objectAtIndex:i];
475 [[vc windowController] showWindow:self];
479 - (IBAction)selectPreviousWindow:(id)sender
481 unsigned i, count = [vimControllers count];
484 NSWindow *keyWindow = [NSApp keyWindow];
485 for (i = 0; i < count; ++i) {
486 MMVimController *vc = [vimControllers objectAtIndex:i];
487 if ([[[vc windowController] window] isEqual:keyWindow])
497 MMVimController *vc = [vimControllers objectAtIndex:i];
498 [[vc windowController] showWindow:self];
502 - (IBAction)fontSizeUp:(id)sender
504 [[NSFontManager sharedFontManager] modifyFont:
505 [NSNumber numberWithInt:NSSizeUpFontAction]];
508 - (IBAction)fontSizeDown:(id)sender
510 [[NSFontManager sharedFontManager] modifyFont:
511 [NSNumber numberWithInt:NSSizeDownFontAction]];
514 - (byref id <MMFrontendProtocol>)
515 connectBackend:(byref in id <MMBackendProtocol>)backend
518 //NSLog(@"Connect backend (pid=%d)", pid);
519 NSNumber *pidKey = [NSNumber numberWithInt:pid];
520 MMVimController *vc = nil;
523 [(NSDistantObject*)backend
524 setProtocolForProxy:@protocol(MMBackendProtocol)];
526 vc = [[[MMVimController alloc]
527 initWithBackend:backend pid:pid] autorelease];
529 if (![vimControllers count]) {
530 // The first window autosaves its position. (The autosaving
531 // features of Cocoa are not used because we need more control over
532 // what is autosaved and when it is restored.)
533 [[vc windowController] setWindowAutosaveKey:MMTopLeftPointKey];
536 [vimControllers addObject:vc];
538 id args = [pidArguments objectForKey:pidKey];
539 if (args && [NSNull null] != args)
540 [self passArguments:args toVimController:vc];
542 // HACK! MacVim does not get activated if it is launched from the
543 // terminal, so we forcibly activate here unless it is an untitled
544 // window opening. Untitled windows are treated differently, else
545 // MacVim would steal the focus if another app was activated while the
546 // untitled window was loading.
547 if (!args || args != [NSNull null])
548 [NSApp activateIgnoringOtherApps:YES];
551 [pidArguments removeObjectForKey:pidKey];
556 @catch (NSException *e) {
557 NSLog(@"Exception caught in %s: \"%@\"", _cmd, e);
560 [vimControllers removeObject:vc];
562 [pidArguments removeObjectForKey:pidKey];
568 - (NSArray *)serverList
570 NSMutableArray *array = [NSMutableArray array];
572 unsigned i, count = [vimControllers count];
573 for (i = 0; i < count; ++i) {
574 MMVimController *controller = [vimControllers objectAtIndex:i];
575 if ([controller serverName])
576 [array addObject:[controller serverName]];
582 @end // MMAppController
587 @implementation MMAppController (MMServices)
589 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
590 error:(NSString **)error
592 if (![[pboard types] containsObject:NSStringPboardType]) {
593 NSLog(@"WARNING: Pasteboard contains no object of type "
594 "NSStringPboardType");
598 MMVimController *vc = [self topmostVimController];
600 // Open a new tab first, since dropString: does not do this.
601 [vc sendMessage:AddNewTabMsgID data:nil];
602 [vc dropString:[pboard stringForType:NSStringPboardType]];
604 // NOTE: There is no window to paste the selection into, so save the
605 // text, open a new window, and paste the text when the next window
606 // opens. (If this is called several times in a row, then all but the
607 // last call might be ignored.)
608 if (openSelectionString) [openSelectionString release];
609 openSelectionString = [[pboard stringForType:NSStringPboardType] copy];
611 [self newWindow:self];
615 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
616 error:(NSString **)error
618 if (![[pboard types] containsObject:NSStringPboardType]) {
619 NSLog(@"WARNING: Pasteboard contains no object of type "
620 "NSStringPboardType");
624 // TODO: Parse multiple filenames and create array with names.
625 NSString *string = [pboard stringForType:NSStringPboardType];
626 string = [string stringByTrimmingCharactersInSet:
627 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
628 string = [string stringByStandardizingPath];
630 NSArray *filenames = [self filterFilesAndNotify:
631 [NSArray arrayWithObject:string]];
632 if ([filenames count] > 0) {
633 MMVimController *vc = nil;
634 if (userData && [userData isEqual:@"Tab"])
635 vc = [self topmostVimController];
638 [vc dropFiles:filenames forceOpen:YES];
640 [self application:NSApp openFiles:filenames];
645 @end // MMAppController (MMServices)
650 @implementation MMAppController (Private)
652 - (MMVimController *)keyVimController
654 NSWindow *keyWindow = [NSApp keyWindow];
656 unsigned i, count = [vimControllers count];
657 for (i = 0; i < count; ++i) {
658 MMVimController *vc = [vimControllers objectAtIndex:i];
659 if ([[[vc windowController] window] isEqual:keyWindow])
667 - (MMVimController *)topmostVimController
669 NSArray *windows = [NSApp orderedWindows];
670 if ([windows count] > 0) {
671 NSWindow *window = [windows objectAtIndex:0];
672 unsigned i, count = [vimControllers count];
673 for (i = 0; i < count; ++i) {
674 MMVimController *vc = [vimControllers objectAtIndex:i];
675 if ([[[vc windowController] window] isEqual:window])
683 - (int)launchVimProcessWithArguments:(NSArray *)args
685 NSString *taskPath = nil;
686 NSArray *taskArgs = nil;
687 NSString *path = [[NSBundle mainBundle] pathForAuxiliaryExecutable:@"Vim"];
690 NSLog(@"ERROR: Vim executable could not be found inside app bundle!");
694 if ([[NSUserDefaults standardUserDefaults] boolForKey:MMLoginShellKey]) {
695 // Run process with a login shell
696 // $SHELL -l -c "exec Vim -g -f args"
697 // (-g for GUI, -f for foreground, i.e. don't fork)
699 NSMutableString *execArg = [NSMutableString
700 stringWithFormat:@"exec \"%@\" -g -f", path];
702 // Append all arguments while making sure that arguments containing
703 // spaces are enclosed in quotes.
704 NSCharacterSet *space = [NSCharacterSet whitespaceCharacterSet];
705 unsigned i, count = [args count];
707 for (i = 0; i < count; ++i) {
708 NSString *arg = [args objectAtIndex:i];
709 if (NSNotFound != [arg rangeOfCharacterFromSet:space].location)
710 [execArg appendFormat:@" \"%@\"", arg];
712 [execArg appendFormat:@" %@", arg];
716 // Launch the process with a login shell so that users environment
717 // settings get sourced. This does not always happen when MacVim is
719 taskArgs = [NSArray arrayWithObjects:@"-l", @"-c", execArg, nil];
720 taskPath = [[[NSProcessInfo processInfo] environment]
721 objectForKey:@"SHELL"];
723 taskPath = @"/bin/sh";
725 // Run process directly:
727 // (-g for GUI, -f for foreground, i.e. don't fork)
729 taskArgs = [NSArray arrayWithObjects:@"-g", @"-f", nil];
731 taskArgs = [taskArgs arrayByAddingObjectsFromArray:args];
734 NSTask *task =[NSTask launchedTaskWithLaunchPath:taskPath
736 //NSLog(@"launch %@ with args=%@ (pid=%d)", taskPath, taskArgs,
737 // [task processIdentifier]);
739 int pid = [task processIdentifier];
741 // If the process has no arguments, then add a null argument to the
742 // pidArguments dictionary. This is later used to detect that a process
743 // without arguments is being launched.
745 [pidArguments setObject:[NSNull null]
746 forKey:[NSNumber numberWithInt:pid]];
751 - (NSArray *)filterFilesAndNotify:(NSArray *)filenames
753 // Go trough 'filenames' array and make sure each file exists. Present
754 // warning dialog if some file was missing.
756 NSString *firstMissingFile = nil;
757 NSMutableArray *files = [NSMutableArray array];
758 unsigned i, count = [filenames count];
760 for (i = 0; i < count; ++i) {
761 NSString *name = [filenames objectAtIndex:i];
762 if ([[NSFileManager defaultManager] fileExistsAtPath:name]) {
763 [files addObject:name];
764 } else if (!firstMissingFile) {
765 firstMissingFile = name;
769 if (firstMissingFile) {
770 NSAlert *alert = [[NSAlert alloc] init];
771 [alert addButtonWithTitle:@"OK"];
774 if ([files count] >= count-1) {
775 [alert setMessageText:@"File not found"];
776 text = [NSString stringWithFormat:@"Could not open file with "
777 "name %@.", firstMissingFile];
779 [alert setMessageText:@"Multiple files not found"];
780 text = [NSString stringWithFormat:@"Could not open file with "
781 "name %@, and %d other files.", firstMissingFile,
782 count-[files count]-1];
785 [alert setInformativeText:text];
786 [alert setAlertStyle:NSWarningAlertStyle];
791 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
797 - (NSArray *)filterOpenFiles:(NSArray *)filenames
798 arguments:(NSDictionary *)args
800 // Check if any of the files in the 'filenames' array are open in any Vim
801 // process. Remove the files that are open from the 'filenames' array and
802 // return it. If all files were filtered out, then raise the first file in
803 // the Vim process it is open. Files that are filtered are sent an odb
804 // open event in case theID is not zero.
806 NSMutableDictionary *localArgs =
807 [NSMutableDictionary dictionaryWithDictionary:args];
808 MMVimController *raiseController = nil;
809 NSString *raiseFile = nil;
810 NSMutableArray *files = [filenames mutableCopy];
811 NSString *expr = [NSString stringWithFormat:
812 @"map([\"%@\"],\"bufloaded(v:val)\")",
813 [files componentsJoinedByString:@"\",\""]];
814 unsigned i, count = [vimControllers count];
816 // Ensure that the files aren't opened when passing arguments.
817 [localArgs setObject:[NSNumber numberWithBool:NO] forKey:@"openFiles"];
819 for (i = 0; i < count && [files count]; ++i) {
820 MMVimController *controller = [vimControllers objectAtIndex:i];
821 id proxy = [controller backendProxy];
824 // Query Vim for which files in the 'files' array are open.
825 NSString *eval = [proxy evaluateExpression:expr];
826 NSIndexSet *idxSet = [NSIndexSet indexSetWithVimList:eval];
827 if ([idxSet count]) {
829 // Remember the file and which Vim that has it open so that
830 // we can raise it later on.
831 raiseController = controller;
832 raiseFile = [files objectAtIndex:[idxSet firstIndex]];
833 [[raiseFile retain] autorelease];
836 // Pass (ODB/Xcode/Spotlight) arguments to this process.
837 [localArgs setObject:[files objectsAtIndexes:idxSet]
838 forKey:@"filenames"];
839 [self passArguments:localArgs toVimController:controller];
841 // Remove all the files that were open in this Vim process and
842 // create a new expression to evaluate.
843 [files removeObjectsAtIndexes:idxSet];
844 expr = [NSString stringWithFormat:
845 @"map([\"%@\"],\"bufloaded(v:val)\")",
846 [files componentsJoinedByString:@"\",\""]];
849 @catch (NSException *e) {
854 if (![files count] && raiseFile) {
855 // Raise the window containing the first file that was already open,
856 // and make sure that the tab containing that file is selected. Only
857 // do this if there are no more files to open, otherwise sometimes the
858 // window with 'raiseFile' will be raised, other times it might be the
859 // window that will open with the files in the 'files' array.
860 raiseFile = [raiseFile stringByEscapingSpecialFilenameCharacters];
861 NSString *input = [NSString stringWithFormat:@"<C-\\><C-N>"
862 ":let oldswb=&swb|let &swb=\"useopen,usetab\"|"
863 "tab sb %@|let &swb=oldswb|unl oldswb|"
864 "cal foreground()|redr|f<CR>", raiseFile];
866 [raiseController addVimInput:input];
872 #if MM_HANDLE_XCODE_MOD_EVENT
873 - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
874 replyEvent:(NSAppleEventDescriptor *)reply
877 // Xcode sends this event to query MacVim which open files have been
879 NSLog(@"reply:%@", reply);
880 NSLog(@"event:%@", event);
882 NSEnumerator *e = [vimControllers objectEnumerator];
884 while ((vc = [e nextObject])) {
885 DescType type = [reply descriptorType];
886 unsigned len = [[type data] length];
887 NSMutableData *data = [NSMutableData data];
889 [data appendBytes:&type length:sizeof(DescType)];
890 [data appendBytes:&len length:sizeof(unsigned)];
891 [data appendBytes:[reply data] length:len];
893 [vc sendMessage:XcodeModMsgID data:data];
899 - (int)findLaunchingProcessWithoutArguments
901 NSArray *keys = [pidArguments allKeysForObject:[NSNull null]];
902 if ([keys count] > 0) {
903 //NSLog(@"found launching process without arguments");
904 return [[keys objectAtIndex:0] intValue];
910 - (MMVimController *)findUntitledWindow
912 NSEnumerator *e = [vimControllers objectEnumerator];
914 while ((vc = [e nextObject])) {
915 // TODO: This is a moronic test...should query the Vim process if there
916 // are any open buffers or something like that instead.
917 NSString *title = [[[vc windowController] window] title];
918 if ([title hasPrefix:@"[No Name]"]) {
919 //NSLog(@"found untitled window");
927 - (NSMutableDictionary *)extractArgumentsFromOdocEvent:
928 (NSAppleEventDescriptor *)desc
930 NSMutableDictionary *dict = [NSMutableDictionary dictionary];
932 // 1. Extract ODB parameters (if any)
933 NSAppleEventDescriptor *odbdesc = desc;
934 if (![odbdesc paramDescriptorForKeyword:keyFileSender]) {
935 // The ODB paramaters may hide inside the 'keyAEPropData' descriptor.
936 odbdesc = [odbdesc paramDescriptorForKeyword:keyAEPropData];
937 if (![odbdesc paramDescriptorForKeyword:keyFileSender])
942 NSAppleEventDescriptor *p =
943 [odbdesc paramDescriptorForKeyword:keyFileSender];
945 [dict setObject:[NSNumber numberWithUnsignedInt:[p typeCodeValue]]
948 p = [odbdesc paramDescriptorForKeyword:keyFileCustomPath];
950 [dict setObject:[p stringValue] forKey:@"remotePath"];
952 p = [odbdesc paramDescriptorForKeyword:keyFileSenderToken];
954 [dict setObject:p forKey:@"remotePath"];
957 // 2. Extract Xcode parameters (if any)
958 NSAppleEventDescriptor *xcodedesc =
959 [desc paramDescriptorForKeyword:keyAEPosition];
962 MMSelectionRange *sr = (MMSelectionRange*)[[xcodedesc data] bytes];
964 if (sr->lineNum < 0) {
965 // Should select a range of lines.
966 range.location = sr->startRange + 1;
967 range.length = sr->endRange - sr->startRange + 1;
969 // Should only move cursor to a line.
970 range.location = sr->lineNum + 1;
974 [dict setObject:NSStringFromRange(range) forKey:@"selectionRange"];
977 // 3. Extract Spotlight search text (if any)
978 NSAppleEventDescriptor *spotlightdesc =
979 [desc paramDescriptorForKeyword:keyAESearchText];
981 [dict setObject:[spotlightdesc stringValue] forKey:@"searchText"];
986 - (void)passArguments:(NSDictionary *)args toVimController:(MMVimController*)vc
990 // Pass filenames to open if required (the 'openFiles' argument can be used
991 // to disallow opening of the files).
992 NSArray *filenames = [args objectForKey:@"filenames"];
993 if (filenames && [[args objectForKey:@"openFiles"] boolValue]) {
994 NSString *tabDrop = buildTabDropCommand(filenames);
995 [vc addVimInput:tabDrop];
999 if (filenames && [args objectForKey:@"remoteID"]) {
1000 [vc odbEdit:filenames
1001 server:[[args objectForKey:@"remoteID"] unsignedIntValue]
1002 path:[args objectForKey:@"remotePath"]
1003 token:[args objectForKey:@"remoteToken"]];
1006 // Pass range of lines to select
1007 if ([args objectForKey:@"selectionRange"]) {
1008 NSRange selectionRange = NSRangeFromString(
1009 [args objectForKey:@"selectionRange"]);
1010 [vc addVimInput:buildSelectRangeCommand(selectionRange)];
1014 NSString *searchText = [args objectForKey:@"searchText"];
1016 [vc addVimInput:buildSearchTextCommand(searchText)];
1019 @end // MMAppController (Private)
1024 @implementation NSMenu (MMExtras)
1026 - (void)recurseSetAutoenablesItems:(BOOL)on
1028 [self setAutoenablesItems:on];
1030 int i, count = [self numberOfItems];
1031 for (i = 0; i < count; ++i) {
1032 NSMenuItem *item = [self itemAtIndex:i];
1033 [item setEnabled:YES];
1034 NSMenu *submenu = [item submenu];
1036 [submenu recurseSetAutoenablesItems:on];
1041 @end // NSMenu (MMExtras)
1046 @implementation NSNumber (MMExtras)
1049 return [self intValue];
1051 @end // NSNumber (MMExtras)