ODB Editor protocol (aka 'external editor') support
[MacVim.git] / src / MacVim / MMAppController.m
bloba488250901207d71cf71eac77a78e33a27bdb382
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
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.
9  */
11  * MMAppController
12  *
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.
17  *
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
22  * MMVimController.
23  *
24  * A Vim process started from the command line connects directly by sending the
25  * connectBackend:pid: message (launchVimProcessWithArguments: is never called
26  * in this case).
27  */
29 #import "MMAppController.h"
30 #import "MMVimController.h"
31 #import "MMWindowController.h"
36 // Default timeout intervals on all connections.
37 static NSTimeInterval MMRequestTimeout = 5;
38 static NSTimeInterval MMReplyTimeout = 5;
42 @interface MMAppController (MMServices)
43 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
44                 error:(NSString **)error;
45 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
46            error:(NSString **)error;
47 @end
50 @interface MMAppController (Private)
51 - (MMVimController *)keyVimController;
52 - (MMVimController *)topmostVimController;
53 - (int)launchVimProcessWithArguments:(NSArray *)args;
54 - (NSArray *)filterFilesAndNotify:(NSArray *)files;
55 - (NSArray *)filterOpenFiles:(NSArray *)filenames remote:(OSType)theID
56                         path:(NSString *)path
57                        token:(NSAppleEventDescriptor *)token;
58 - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
59                  replyEvent:(NSAppleEventDescriptor *)reply;
60 @end
62 @interface NSMenu (MMExtras)
63 - (void)recurseSetAutoenablesItems:(BOOL)on;
64 @end
66 @interface NSNumber (MMExtras)
67 - (int)tag;
68 @end
72 @implementation MMAppController
74 + (void)initialize
76     NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
77         [NSNumber numberWithBool:NO],   MMNoWindowKey,
78         [NSNumber numberWithInt:64],    MMTabMinWidthKey,
79         [NSNumber numberWithInt:6*64],  MMTabMaxWidthKey,
80         [NSNumber numberWithInt:132],   MMTabOptimumWidthKey,
81         [NSNumber numberWithInt:2],     MMTextInsetLeftKey,
82         [NSNumber numberWithInt:1],     MMTextInsetRightKey,
83         [NSNumber numberWithInt:1],     MMTextInsetTopKey,
84         [NSNumber numberWithInt:1],     MMTextInsetBottomKey,
85         [NSNumber numberWithBool:NO],   MMTerminateAfterLastWindowClosedKey,
86         @"MMTypesetter",                MMTypesetterKey,
87         [NSNumber numberWithFloat:1],   MMCellWidthMultiplierKey,
88         [NSNumber numberWithFloat:-1],  MMBaselineOffsetKey,
89         [NSNumber numberWithBool:YES],  MMTranslateCtrlClickKey,
90         [NSNumber numberWithBool:NO],   MMOpenFilesInTabsKey,
91         [NSNumber numberWithBool:NO],   MMNoFontSubstitutionKey,
92         [NSNumber numberWithBool:NO],   MMLoginShellKey,
93         nil];
95     [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
97     NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
98     [NSApp registerServicesMenuSendTypes:types returnTypes:types];
101 - (id)init
103     if ((self = [super init])) {
104         fontContainerRef = loadFonts();
106         vimControllers = [NSMutableArray new];
107         pidArguments = [NSMutableDictionary new];
109         // NOTE!  If the name of the connection changes here it must also be
110         // updated in MMBackend.m.
111         NSConnection *connection = [NSConnection defaultConnection];
112         NSString *name = [NSString stringWithFormat:@"%@-connection",
113                  [[NSBundle mainBundle] bundleIdentifier]];
114         //NSLog(@"Registering connection with name '%@'", name);
115         if ([connection registerName:name]) {
116             [connection setRequestTimeout:MMRequestTimeout];
117             [connection setReplyTimeout:MMReplyTimeout];
118             [connection setRootObject:self];
120             // NOTE: When the user is resizing the window the AppKit puts the
121             // run loop in event tracking mode.  Unless the connection listens
122             // to request in this mode, live resizing won't work.
123             [connection addRequestMode:NSEventTrackingRunLoopMode];
124         } else {
125             NSLog(@"WARNING: Failed to register connection with name '%@'",
126                     name);
127         }
128     }
130     return self;
133 - (void)dealloc
135     //NSLog(@"MMAppController dealloc");
137     [pidArguments release];
138     [vimControllers release];
139     [openSelectionString release];
141     [super dealloc];
144 - (void)applicationWillFinishLaunching:(NSNotification *)notification
146     [[NSAppleEventManager sharedAppleEventManager]
147             setEventHandler:self
148                 andSelector:@selector(handleXcodeModEvent:replyEvent:)
149               forEventClass:'KAHL'
150                  andEventID:'MOD '];
153 - (void)applicationDidFinishLaunching:(NSNotification *)notification
155     [NSApp setServicesProvider:self];
158 - (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
160     // NOTE!  This way it possible to start the app with the command-line
161     // argument '-nowindow yes' and no window will be opened by default.
162     untitledWindowOpening =
163         ![[NSUserDefaults standardUserDefaults] boolForKey:MMNoWindowKey];
164     return untitledWindowOpening;
167 - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
169     [self newWindow:self];
170     return YES;
173 - (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
175     OSType remoteID;
176     NSString *remotePath;
177     NSAppleEventDescriptor *remoteToken;
178     NSAppleEventDescriptor *odbdesc =
179         [[NSAppleEventManager sharedAppleEventManager] currentAppleEvent];
181     if (![odbdesc paramDescriptorForKeyword:keyFileSender]) {
182         // The ODB paramaters may hide inside the 'keyAEPropData' descriptor.
183         odbdesc = [odbdesc paramDescriptorForKeyword:keyAEPropData];
184         if (![odbdesc paramDescriptorForKeyword:keyFileSender])
185             odbdesc = nil;
186     }
188     if (odbdesc) {
189         remoteID = [[odbdesc paramDescriptorForKeyword:keyFileSender]
190                 typeCodeValue];
191         remotePath = [[odbdesc paramDescriptorForKeyword:keyFileCustomPath]
192                 stringValue];
193         remoteToken = [[odbdesc paramDescriptorForKeyword:keyFileSenderToken]
194                 copy];
196         //NSLog(@"ODB parameters: ID=0x%x path=%@ token=%@",
197         //        remoteID, remotePath, remoteToken);
198     }
200     filenames = [self filterOpenFiles:filenames remote:remoteID path:remotePath
201                                 token:remoteToken];
202     if ([filenames count]) {
203         MMVimController *vc;
204         BOOL openInTabs = [[NSUserDefaults standardUserDefaults]
205             boolForKey:MMOpenFilesInTabsKey];
207         if (openInTabs && (vc = [self topmostVimController])) {
208             // Open files in tabs in the topmost window.
209             [vc dropFiles:filenames forceOpen:YES];
210             if (odbdesc)
211                 [vc odbEdit:filenames server:remoteID path:remotePath
212                       token:remoteToken];
213         } else {
214             // Open files in tabs in a new window.
215             NSMutableArray *args = [NSMutableArray arrayWithObject:@"-p"];
216             [args addObjectsFromArray:filenames];
217             int pid = [self launchVimProcessWithArguments:args];
219             // The Vim process starts asynchronously.  Some arguments cannot be
220             // on the command line, so store them in a dictionary and pass them
221             // to the process once it has started.
222             //
223             // TODO: If the Vim process fails to start, or if it changes PID,
224             // then the memory allocated for these parameters will leak.
225             // Ensure that this cannot happen or somehow detect it.
226             if (odbdesc) {
227                 // The remote token can be arbitrary data so it is cannot
228                 // (without encoding it as text) be passed on the command line.
229                 NSMutableDictionary *args =
230                     [NSMutableDictionary dictionaryWithObjectsAndKeys:
231                         filenames, @"filenames",
232                         [NSNumber numberWithUnsignedInt:remoteID], @"remoteID",
233                         nil];
234                 if (remotePath)
235                     [args setObject:remotePath forKey:@"remotePath"];
236                 if (remoteToken)
237                     [args setObject:remoteToken forKey:@"remoteToken"];
239                 [pidArguments setObject:args
240                                  forKey:[NSNumber numberWithInt:pid]];
241             }
242         }
243     }
245     [NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
246     // NSApplicationDelegateReplySuccess = 0,
247     // NSApplicationDelegateReplyCancel = 1,
248     // NSApplicationDelegateReplyFailure = 2
251 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
253     return [[NSUserDefaults standardUserDefaults]
254             boolForKey:MMTerminateAfterLastWindowClosedKey];
257 - (NSApplicationTerminateReply)applicationShouldTerminate:
258     (NSApplication *)sender
260     // TODO: Follow Apple's guidelines for 'Graceful Application Termination'
261     // (in particular, allow user to review changes and save).
262     int reply = NSTerminateNow;
263     BOOL modifiedBuffers = NO;
265     // Go through windows, checking for modified buffers.  (Each Vim process
266     // tells MacVim when any buffer has been modified and MacVim sets the
267     // 'documentEdited' flag of the window correspondingly.)
268     NSEnumerator *e = [[NSApp windows] objectEnumerator];
269     id window;
270     while ((window = [e nextObject])) {
271         if ([window isDocumentEdited]) {
272             modifiedBuffers = YES;
273             break;
274         }
275     }
277     if (modifiedBuffers) {
278         NSAlert *alert = [[NSAlert alloc] init];
279         [alert addButtonWithTitle:@"Quit"];
280         [alert addButtonWithTitle:@"Cancel"];
281         [alert setMessageText:@"Quit without saving?"];
282         [alert setInformativeText:@"There are modified buffers, "
283             "if you quit now all changes will be lost.  Quit anyway?"];
284         [alert setAlertStyle:NSWarningAlertStyle];
286         if ([alert runModal] != NSAlertFirstButtonReturn)
287             reply = NSTerminateCancel;
289         [alert release];
290     }
292     // Tell all Vim processes to terminate now (otherwise they'll leave swap
293     // files behind).
294     if (NSTerminateNow == reply) {
295         e = [vimControllers objectEnumerator];
296         id vc;
297         while ((vc = [e nextObject]))
298             [vc sendMessage:TerminateNowMsgID data:nil];
299     }
301     return reply;
304 - (void)applicationWillTerminate:(NSNotification *)notification
306     [[NSAppleEventManager sharedAppleEventManager]
307             removeEventHandlerForEventClass:'KAHL'
308                                  andEventID:'MOD '];
310     // This will invalidate all connections (since they were spawned from the
311     // default connection).
312     [[NSConnection defaultConnection] invalidate];
314     // Send a SIGINT to all running Vim processes, so that they are sure to
315     // receive the connectionDidDie: notification (a process has to be checking
316     // the run-loop for this to happen).
317     unsigned i, count = [vimControllers count];
318     for (i = 0; i < count; ++i) {
319         MMVimController *controller = [vimControllers objectAtIndex:i];
320         int pid = [controller pid];
321         if (pid > 0)
322             kill(pid, SIGINT);
323     }
325     if (fontContainerRef) {
326         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
327         fontContainerRef = 0;
328     }
330     // TODO: Is this a correct way of releasing the MMAppController?
331     // (It doesn't seem like dealloc is ever called.)
332     [NSApp setDelegate:nil];
333     [self autorelease];
336 - (void)removeVimController:(id)controller
338     //NSLog(@"%s%@", _cmd, controller);
340     [[controller windowController] close];
342     [vimControllers removeObject:controller];
344     if (![vimControllers count]) {
345         // Turn on autoenabling of menus (because no Vim is open to handle it),
346         // but do not touch the MacVim menu.  Note that the menus must be
347         // enabled first otherwise autoenabling does not work.
348         NSMenu *mainMenu = [NSApp mainMenu];
349         int i, count = [mainMenu numberOfItems];
350         for (i = 1; i < count; ++i) {
351             NSMenuItem *item = [mainMenu itemAtIndex:i];
352             [item setEnabled:YES];
353             [[item submenu] recurseSetAutoenablesItems:YES];
354         }
355     }
358 - (void)windowControllerWillOpen:(MMWindowController *)windowController
360     NSPoint topLeft = NSZeroPoint;
361     NSWindow *keyWin = [NSApp keyWindow];
362     NSWindow *win = [windowController window];
364     if (!win) return;
366     // If there is a key window, cascade from it, otherwise use the autosaved
367     // window position (if any).
368     if (keyWin) {
369         NSRect frame = [keyWin frame];
370         topLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
371     } else {
372         NSString *topLeftString = [[NSUserDefaults standardUserDefaults]
373             stringForKey:MMTopLeftPointKey];
374         if (topLeftString)
375             topLeft = NSPointFromString(topLeftString);
376     }
378     if (!NSEqualPoints(topLeft, NSZeroPoint)) {
379         if (keyWin)
380             topLeft = [win cascadeTopLeftFromPoint:topLeft];
382         [win setFrameTopLeftPoint:topLeft];
383     }
385     if (openSelectionString) {
386         // There is some text to paste into this window as a result of the
387         // services menu "Open selection ..." being used.
388         [[windowController vimController] dropString:openSelectionString];
389         [openSelectionString release];
390         openSelectionString = nil;
391     }
394 - (IBAction)newWindow:(id)sender
396     [self launchVimProcessWithArguments:nil];
399 - (IBAction)selectNextWindow:(id)sender
401     unsigned i, count = [vimControllers count];
402     if (!count) return;
404     NSWindow *keyWindow = [NSApp keyWindow];
405     for (i = 0; i < count; ++i) {
406         MMVimController *vc = [vimControllers objectAtIndex:i];
407         if ([[[vc windowController] window] isEqual:keyWindow])
408             break;
409     }
411     if (i < count) {
412         if (++i >= count)
413             i = 0;
414         MMVimController *vc = [vimControllers objectAtIndex:i];
415         [[vc windowController] showWindow:self];
416     }
419 - (IBAction)selectPreviousWindow:(id)sender
421     unsigned i, count = [vimControllers count];
422     if (!count) return;
424     NSWindow *keyWindow = [NSApp keyWindow];
425     for (i = 0; i < count; ++i) {
426         MMVimController *vc = [vimControllers objectAtIndex:i];
427         if ([[[vc windowController] window] isEqual:keyWindow])
428             break;
429     }
431     if (i < count) {
432         if (i > 0) {
433             --i;
434         } else {
435             i = count - 1;
436         }
437         MMVimController *vc = [vimControllers objectAtIndex:i];
438         [[vc windowController] showWindow:self];
439     }
442 - (IBAction)fontSizeUp:(id)sender
444     [[NSFontManager sharedFontManager] modifyFont:
445             [NSNumber numberWithInt:NSSizeUpFontAction]];
448 - (IBAction)fontSizeDown:(id)sender
450     [[NSFontManager sharedFontManager] modifyFont:
451             [NSNumber numberWithInt:NSSizeDownFontAction]];
454 - (byref id <MMFrontendProtocol>)
455     connectBackend:(byref in id <MMBackendProtocol>)backend
456                pid:(int)pid
458     //NSLog(@"Connect backend (pid=%d)", pid);
460     [(NSDistantObject*)backend
461             setProtocolForProxy:@protocol(MMBackendProtocol)];
463     MMVimController *vc = [[[MMVimController alloc]
464             initWithBackend:backend pid:pid] autorelease];
466     if (![vimControllers count]) {
467         // The first window autosaves its position.  (The autosaving features
468         // of Cocoa are not used because we need more control over what is
469         // autosaved and when it is restored.)
470         [[vc windowController] setWindowAutosaveKey:MMTopLeftPointKey];
471     }
473     [vimControllers addObject:vc];
475     // HACK!  MacVim does not get activated if it is launched from the
476     // terminal, so we forcibly activate here unless it is an untitled window
477     // opening (i.e. MacVim was opened from the Finder).  Untitled windows are
478     // treated differently, else MacVim would steal the focus if another app
479     // was activated while the untitled window was loading.
480     if (!untitledWindowOpening)
481         [NSApp activateIgnoringOtherApps:YES];
483     untitledWindowOpening = NO;
485     // Arguments to a new Vim process that cannot be passed on the command line
486     // are stored in a dictionary and passed to the Vim process here.
487     NSNumber *key = [NSNumber numberWithInt:pid];
488     NSDictionary *args = [pidArguments objectForKey:key];
489     if (args) {
490         if ([args objectForKey:@"remoteID"]) {
491             [vc odbEdit:[args objectForKey:@"filenames"]
492                  server:[[args objectForKey:@"remoteID"] unsignedIntValue]
493                    path:[args objectForKey:@"remotePath"]
494                   token:[args objectForKey:@"remoteToken"]];
495         }
497         [pidArguments removeObjectForKey:key];
498     }
500     return vc;
503 - (NSArray *)serverList
505     NSMutableArray *array = [NSMutableArray array];
507     unsigned i, count = [vimControllers count];
508     for (i = 0; i < count; ++i) {
509         MMVimController *controller = [vimControllers objectAtIndex:i];
510         if ([controller serverName])
511             [array addObject:[controller serverName]];
512     }
514     return array;
517 @end // MMAppController
522 @implementation MMAppController (MMServices)
524 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
525                 error:(NSString **)error
527     if (![[pboard types] containsObject:NSStringPboardType]) {
528         NSLog(@"WARNING: Pasteboard contains no object of type "
529                 "NSStringPboardType");
530         return;
531     }
533     MMVimController *vc = [self topmostVimController];
534     if (vc) {
535         // Open a new tab first, since dropString: does not do this.
536         [vc sendMessage:AddNewTabMsgID data:nil];
537         [vc dropString:[pboard stringForType:NSStringPboardType]];
538     } else {
539         // NOTE: There is no window to paste the selection into, so save the
540         // text, open a new window, and paste the text when the next window
541         // opens.  (If this is called several times in a row, then all but the
542         // last call might be ignored.)
543         if (openSelectionString) [openSelectionString release];
544         openSelectionString = [[pboard stringForType:NSStringPboardType] copy];
546         [self newWindow:self];
547     }
550 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
551            error:(NSString **)error
553     if (![[pboard types] containsObject:NSStringPboardType]) {
554         NSLog(@"WARNING: Pasteboard contains no object of type "
555                 "NSStringPboardType");
556         return;
557     }
559     // TODO: Parse multiple filenames and create array with names.
560     NSString *string = [pboard stringForType:NSStringPboardType];
561     string = [string stringByTrimmingCharactersInSet:
562             [NSCharacterSet whitespaceAndNewlineCharacterSet]];
563     string = [string stringByStandardizingPath];
565     NSArray *filenames = [self filterFilesAndNotify:
566             [NSArray arrayWithObject:string]];
567     if ([filenames count] > 0) {
568         MMVimController *vc = nil;
569         if (userData && [userData isEqual:@"Tab"])
570             vc = [self topmostVimController];
572         if (vc) {
573             [vc dropFiles:filenames forceOpen:YES];
574         } else {
575             [self application:NSApp openFiles:filenames];
576         }
577     }
580 @end // MMAppController (MMServices)
585 @implementation MMAppController (Private)
587 - (MMVimController *)keyVimController
589     NSWindow *keyWindow = [NSApp keyWindow];
590     if (keyWindow) {
591         unsigned i, count = [vimControllers count];
592         for (i = 0; i < count; ++i) {
593             MMVimController *vc = [vimControllers objectAtIndex:i];
594             if ([[[vc windowController] window] isEqual:keyWindow])
595                 return vc;
596         }
597     }
599     return nil;
602 - (MMVimController *)topmostVimController
604     NSArray *windows = [NSApp orderedWindows];
605     if ([windows count] > 0) {
606         NSWindow *window = [windows objectAtIndex:0];
607         unsigned i, count = [vimControllers count];
608         for (i = 0; i < count; ++i) {
609             MMVimController *vc = [vimControllers objectAtIndex:i];
610             if ([[[vc windowController] window] isEqual:window])
611                 return vc;
612         }
613     }
615     return nil;
618 - (int)launchVimProcessWithArguments:(NSArray *)args
620     NSString *taskPath = nil;
621     NSArray *taskArgs = nil;
622     NSString *path = [[NSBundle mainBundle] pathForAuxiliaryExecutable:@"Vim"];
624     if (!path) {
625         NSLog(@"ERROR: Vim executable could not be found inside app bundle!");
626         return 0;
627     }
629     if ([[NSUserDefaults standardUserDefaults] boolForKey:MMLoginShellKey]) {
630         // Run process with a login shell
631         //   $SHELL -l -c "exec Vim args"
633         NSMutableString *execArg = [NSMutableString
634             stringWithFormat:@"exec \"%@\" -g", path];
635         if (args) {
636             // Append all arguments while making sure that arguments containing
637             // spaces are enclosed in quotes.
638             NSCharacterSet *space = [NSCharacterSet whitespaceCharacterSet];
639             unsigned i, count = [args count];
641             for (i = 0; i < count; ++i) {
642                 NSString *arg = [args objectAtIndex:i];
643                 if (NSNotFound != [arg rangeOfCharacterFromSet:space].location)
644                     [execArg appendFormat:@" \"%@\"", arg];
645                 else
646                     [execArg appendFormat:@" %@", arg];
647             }
648         }
650         // Launch the process with a login shell so that users environment
651         // settings get sourced.  This does not always happen when MacVim is
652         // started.
653         taskArgs = [NSArray arrayWithObjects:@"-l", @"-c", execArg, nil];
654         taskPath = [[[NSProcessInfo processInfo] environment]
655             objectForKey:@"SHELL"];
656         if (!taskPath)
657             taskPath = @"/bin/sh";
658     } else {
659         // Run process directly:
660         //   Vim args
661         taskPath = path;
662         taskArgs = [NSArray arrayWithObject:@"-g"];
663         if (args)
664             taskArgs = [taskArgs arrayByAddingObjectsFromArray:args];
665     }
667     NSTask *task =[NSTask launchedTaskWithLaunchPath:taskPath
668                                            arguments:taskArgs];
669     //NSLog(@"launch %@ with args=%@ (pid=%d)", [task processIdentifier],
670     //    taskPath, taskArgs);
672     return [task processIdentifier];
675 - (NSArray *)filterFilesAndNotify:(NSArray *)filenames
677     // Go trough 'filenames' array and make sure each file exists.  Present
678     // warning dialog if some file was missing.
680     NSString *firstMissingFile = nil;
681     NSMutableArray *files = [NSMutableArray array];
682     unsigned i, count = [filenames count];
684     for (i = 0; i < count; ++i) {
685         NSString *name = [filenames objectAtIndex:i];
686         if ([[NSFileManager defaultManager] fileExistsAtPath:name]) {
687             [files addObject:name];
688         } else if (!firstMissingFile) {
689             firstMissingFile = name;
690         }
691     }
693     if (firstMissingFile) {
694         NSAlert *alert = [[NSAlert alloc] init];
695         [alert addButtonWithTitle:@"OK"];
697         NSString *text;
698         if ([files count] >= count-1) {
699             [alert setMessageText:@"File not found"];
700             text = [NSString stringWithFormat:@"Could not open file with "
701                 "name %@.", firstMissingFile];
702         } else {
703             [alert setMessageText:@"Multiple files not found"];
704             text = [NSString stringWithFormat:@"Could not open file with "
705                 "name %@, and %d other files.", firstMissingFile,
706                 count-[files count]-1];
707         }
709         [alert setInformativeText:text];
710         [alert setAlertStyle:NSWarningAlertStyle];
712         [alert runModal];
713         [alert release];
715         [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
716     }
718     return files;
721 - (NSArray *)filterOpenFiles:(NSArray *)filenames remote:(OSType)theID
722                         path:(NSString *)path
723                        token:(NSAppleEventDescriptor *)token
725     // Check if any of the files in the 'filenames' array are open in any Vim
726     // process.  Remove the files that are open from the 'filenames' array and
727     // return it.  If all files were filtered out, then raise the first file in
728     // the Vim process it is open.  Files that are filtered are sent an odb
729     // open event in case theID is not zero.
731     MMVimController *raiseController = nil;
732     NSString *raiseFile = nil;
733     NSMutableArray *files = [filenames mutableCopy];
734     NSString *expr = [NSString stringWithFormat:
735             @"map([\"%@\"],\"bufloaded(v:val)\")",
736             [files componentsJoinedByString:@"\",\""]];
737     unsigned i, count = [vimControllers count];
739     for (i = 0; i < count && [files count]; ++i) {
740         MMVimController *controller = [vimControllers objectAtIndex:i];
741         id proxy = [controller backendProxy];
743         @try {
744             NSString *eval = [proxy evaluateExpression:expr];
745             NSIndexSet *idxSet = [NSIndexSet indexSetWithVimList:eval];
746             if ([idxSet count]) {
747                 if (!raiseFile) {
748                     // Remember the file and which Vim that has it open so that
749                     // we can raise it later on.
750                     raiseController = controller;
751                     raiseFile = [files objectAtIndex:[idxSet firstIndex]];
752                     [[raiseFile retain] autorelease];
753                 }
755                 // Send an odb open event to the Vim process.
756                 if (theID != 0)
757                     [controller odbEdit:[files objectsAtIndexes:idxSet]
758                                  server:theID path:path token:token];
760                 // Remove all the files that were open in this Vim process and
761                 // create a new expression to evaluate.
762                 [files removeObjectsAtIndexes:idxSet];
763                 expr = [NSString stringWithFormat:
764                         @"map([\"%@\"],\"bufloaded(v:val)\")",
765                         [files componentsJoinedByString:@"\",\""]];
766             }
767         }
768         @catch (NSException *e) {
769             // Do nothing ...
770         }
771     }
773     if (![files count] && raiseFile) {
774         // Raise the window containing the first file that was already open,
775         // and make sure that the tab containing that file is selected.  Only
776         // do this if there are no more files to open, otherwise sometimes the
777         // window with 'raiseFile' will be raised, other times it might be the
778         // window that will open with the files in the 'files' array.
779         raiseFile = [raiseFile stringByEscapingSpecialFilenameCharacters];
780         NSString *input = [NSString stringWithFormat:@"<C-\\><C-N>"
781             ":let oldswb=&swb|let &swb=\"useopen,usetab\"|"
782             "tab sb %@|let &swb=oldswb|unl oldswb|"
783             "cal foreground()|redr|f<CR>", raiseFile];
784         [raiseController addVimInput:input];
785     }
787     return files;
790 - (void)handleXcodeModEvent:(NSAppleEventDescriptor *)event
791                  replyEvent:(NSAppleEventDescriptor *)reply
793 #if 0
794     // Xcode sends this event to query MacVim which open files have been
795     // modified.
796     NSLog(@"reply:%@", reply);
797     NSLog(@"event:%@", event);
799     NSEnumerator *e = [vimControllers objectEnumerator];
800     id vc;
801     while ((vc = [e nextObject])) {
802         DescType type = [reply descriptorType];
803         unsigned len = [[type data] length];
804         NSMutableData *data = [NSMutableData data];
806         [data appendBytes:&type length:sizeof(DescType)];
807         [data appendBytes:&len length:sizeof(unsigned)];
808         [data appendBytes:[reply data] length:len];
810         [vc sendMessage:XcodeModMsgID data:data];
811     }
812 #endif
815 @end // MMAppController (Private)
820 @implementation NSMenu (MMExtras)
822 - (void)recurseSetAutoenablesItems:(BOOL)on
824     [self setAutoenablesItems:on];
826     int i, count = [self numberOfItems];
827     for (i = 0; i < count; ++i) {
828         NSMenuItem *item = [self itemAtIndex:i];
829         [item setEnabled:YES];
830         NSMenu *submenu = [item submenu];
831         if (submenu) {
832             [submenu recurseSetAutoenablesItems:on];
833         }
834     }
837 @end  // NSMenu (MMExtras)
842 @implementation NSNumber (MMExtras)
843 - (int)tag
845     return [self intValue];
847 @end // NSNumber (MMExtras)