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.
11 #import "MMAppController.h"
12 #import "MMVimController.h"
13 #import "MMWindowController.h"
17 // Default timeout intervals on all connections.
18 static NSTimeInterval MMRequestTimeout = 5;
19 static NSTimeInterval MMReplyTimeout = 5;
23 @interface MMAppController (MMServices)
24 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
25 error:(NSString **)error;
26 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
27 error:(NSString **)error;
31 @interface MMAppController (Private)
32 - (MMVimController *)keyVimController;
33 - (MMVimController *)topmostVimController;
34 - (void)launchVimProcessWithArguments:(NSArray *)args;
37 @interface NSMenu (MMExtras)
38 - (void)recurseSetAutoenablesItems:(BOOL)on;
41 @interface NSNumber (MMExtras)
47 @implementation MMAppController
51 NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
52 [NSNumber numberWithBool:NO], MMNoWindowKey,
53 [NSNumber numberWithInt:64], MMTabMinWidthKey,
54 [NSNumber numberWithInt:6*64], MMTabMaxWidthKey,
55 [NSNumber numberWithInt:132], MMTabOptimumWidthKey,
56 [NSNumber numberWithInt:2], MMTextInsetLeftKey,
57 [NSNumber numberWithInt:1], MMTextInsetRightKey,
58 [NSNumber numberWithInt:1], MMTextInsetTopKey,
59 [NSNumber numberWithInt:1], MMTextInsetBottomKey,
60 [NSNumber numberWithBool:NO], MMTerminateAfterLastWindowClosedKey,
61 @"MMTypesetter", MMTypesetterKey,
62 [NSNumber numberWithFloat:1], MMCellWidthMultiplierKey,
63 [NSNumber numberWithFloat:-1], MMBaselineOffsetKey,
64 [NSNumber numberWithBool:YES], MMTranslateCtrlClickKey,
65 [NSNumber numberWithBool:NO], MMOpenFilesInTabsKey,
68 [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
70 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
71 [NSApp registerServicesMenuSendTypes:types returnTypes:types];
76 if ((self = [super init])) {
77 fontContainerRef = loadFonts();
79 vimControllers = [NSMutableArray new];
81 // NOTE! If the name of the connection changes here it must also be
82 // updated in MMBackend.m.
83 NSConnection *connection = [NSConnection defaultConnection];
84 NSString *name = [NSString stringWithFormat:@"%@-connection",
85 [[NSBundle mainBundle] bundleIdentifier]];
86 //NSLog(@"Registering connection with name '%@'", name);
87 if ([connection registerName:name]) {
88 [connection setRequestTimeout:MMRequestTimeout];
89 [connection setReplyTimeout:MMReplyTimeout];
90 [connection setRootObject:self];
92 // NOTE: When the user is resizing the window the AppKit puts the
93 // run loop in event tracking mode. Unless the connection listens
94 // to request in this mode, live resizing won't work.
95 [connection addRequestMode:NSEventTrackingRunLoopMode];
97 NSLog(@"WARNING: Failed to register connection with name '%@'",
107 //NSLog(@"MMAppController dealloc");
109 [vimControllers release];
110 [openSelectionString release];
115 - (void)applicationDidFinishLaunching:(NSNotification *)notification
117 [NSApp setServicesProvider:self];
120 - (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
122 // NOTE! This way it possible to start the app with the command-line
123 // argument '-nowindow yes' and no window will be opened by default.
124 untitledWindowOpening =
125 ![[NSUserDefaults standardUserDefaults] boolForKey:MMNoWindowKey];
126 return untitledWindowOpening;
129 - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
131 //NSLog(@"%s NSapp=%@ theApp=%@", _cmd, NSApp, sender);
133 [self newWindow:self];
137 - (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
139 // Go trough 'filenames' array and make sure each file exists.
140 NSString *firstMissingFile = nil;
141 NSMutableArray *files = [NSMutableArray array];
142 int i, count = [filenames count];
143 for (i = 0; i < count; ++i) {
144 NSString *name = [filenames objectAtIndex:i];
145 if ([[NSFileManager defaultManager] fileExistsAtPath:name]) {
146 [files addObject:name];
147 } else if (!firstMissingFile) {
148 firstMissingFile = name;
152 if (firstMissingFile) {
153 NSAlert *alert = [[NSAlert alloc] init];
154 [alert addButtonWithTitle:@"OK"];
157 if ([files count] >= count-1) {
158 [alert setMessageText:@"File not found"];
159 text = [NSString stringWithFormat:@"Could not open file with "
160 "name %@.", firstMissingFile];
162 [alert setMessageText:@"Multiple files not found"];
163 text = [NSString stringWithFormat:@"Could not open file with "
164 "name %@, and %d other files.", firstMissingFile,
165 count-[files count]-1];
168 [alert setInformativeText:text];
169 [alert setAlertStyle:NSWarningAlertStyle];
174 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
178 if ([files count] == 1) {
179 // Check if the file is already open...if so raise that window.
180 i, count = [vimControllers count];
181 for (i = 0; i < count; ++i) {
182 MMVimController *controller = [vimControllers objectAtIndex:i];
183 id proxy = [controller backendProxy];
186 NSString *expr = [NSString stringWithFormat:
187 @"bufloaded(\"%@\")", [files objectAtIndex:0]];
188 NSString *eval = [proxy evaluateExpression:expr];
189 if ([eval isEqual:@"1"]) {
190 // TODO: Select the tab with 'file' open.
191 [controller addVimInput:
192 @"<C-\\><C-N>:cal foreground()<CR>"];
196 @catch (NSException *e) {
203 BOOL openInTabs = [[NSUserDefaults standardUserDefaults]
204 boolForKey:MMOpenFilesInTabsKey];
206 if (openInTabs && (vc = [self topmostVimController])) {
207 [vc dropFiles:files];
209 NSMutableArray *args = [NSMutableArray arrayWithObject:@"-p"];
210 [args addObjectsFromArray:files];
211 [self launchVimProcessWithArguments:args];
214 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
215 // NSApplicationDelegateReplySuccess = 0,
216 // NSApplicationDelegateReplyCancel = 1,
217 // NSApplicationDelegateReplyFailure = 2
220 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
222 return [[NSUserDefaults standardUserDefaults]
223 boolForKey:MMTerminateAfterLastWindowClosedKey];
226 - (NSApplicationTerminateReply)applicationShouldTerminate:
227 (NSApplication *)sender
229 // TODO: Follow Apple's guidelines for 'Graceful Application Termination'
230 // (in particular, allow user to review changes and save).
231 int reply = NSTerminateNow;
232 BOOL modifiedBuffers = NO;
234 // Go through windows, checking for modified buffers. (Each Vim process
235 // tells MacVim when any buffer has been modified and MacVim sets the
236 // 'documentEdited' flag of the window correspondingly.)
237 NSEnumerator *e = [[NSApp windows] objectEnumerator];
239 while (window = [e nextObject]) {
240 if ([window isDocumentEdited]) {
241 modifiedBuffers = YES;
246 if (modifiedBuffers) {
247 NSAlert *alert = [[NSAlert alloc] init];
248 [alert addButtonWithTitle:@"Quit"];
249 [alert addButtonWithTitle:@"Cancel"];
250 [alert setMessageText:@"Quit without saving?"];
251 [alert setInformativeText:@"There are modified buffers, "
252 "if you quit now all changes will be lost. Quit anyway?"];
253 [alert setAlertStyle:NSWarningAlertStyle];
255 if ([alert runModal] != NSAlertFirstButtonReturn)
256 reply = NSTerminateCancel;
264 - (void)applicationWillTerminate:(NSNotification *)aNotification
266 // This will invalidate all connections (since they were spawned from the
267 // default connection).
268 [[NSConnection defaultConnection] invalidate];
270 // Send a SIGINT to all running Vim processes, so that they are sure to
271 // receive the connectionDidDie: notification (a process has to be checking
272 // the run-loop for this to happen).
273 unsigned i, count = [vimControllers count];
274 for (i = 0; i < count; ++i) {
275 MMVimController *controller = [vimControllers objectAtIndex:i];
276 int pid = [controller pid];
281 if (fontContainerRef) {
282 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
283 fontContainerRef = 0;
286 // TODO: Is this a correct way of releasing the MMAppController?
287 // (It doesn't seem like dealloc is ever called.)
288 [NSApp setDelegate:nil];
292 - (void)removeVimController:(id)controller
294 //NSLog(@"%s%@", _cmd, controller);
296 [[controller windowController] close];
298 [vimControllers removeObject:controller];
300 if (![vimControllers count]) {
301 // Turn on autoenabling of menus (because no Vim is open to handle it),
302 // but do not touch the MacVim menu. Note that the menus must be
303 // enabled first otherwise autoenabling does not work.
304 NSMenu *mainMenu = [NSApp mainMenu];
305 int i, count = [mainMenu numberOfItems];
306 for (i = 1; i < count; ++i) {
307 NSMenuItem *item = [mainMenu itemAtIndex:i];
308 [item setEnabled:YES];
309 [[item submenu] recurseSetAutoenablesItems:YES];
314 - (void)windowControllerWillOpen:(MMWindowController *)windowController
316 NSPoint topLeft = NSZeroPoint;
317 NSWindow *keyWin = [NSApp keyWindow];
318 NSWindow *win = [windowController window];
322 // If there is a key window, cascade from it, otherwise use the autosaved
323 // window position (if any).
325 NSRect frame = [keyWin frame];
326 topLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
328 NSString *topLeftString = [[NSUserDefaults standardUserDefaults]
329 stringForKey:MMTopLeftPointKey];
331 topLeft = NSPointFromString(topLeftString);
334 if (!NSEqualPoints(topLeft, NSZeroPoint)) {
336 topLeft = [win cascadeTopLeftFromPoint:topLeft];
338 [win setFrameTopLeftPoint:topLeft];
341 if (openSelectionString) {
342 // There is some text to paste into this window as a result of the
343 // services menu "Open selection ..." being used.
344 [[windowController vimController] dropString:openSelectionString];
345 [openSelectionString release];
346 openSelectionString = nil;
350 - (IBAction)newWindow:(id)sender
352 [self launchVimProcessWithArguments:nil];
355 - (IBAction)selectNextWindow:(id)sender
357 unsigned i, count = [vimControllers count];
360 NSWindow *keyWindow = [NSApp keyWindow];
361 for (i = 0; i < count; ++i) {
362 MMVimController *vc = [vimControllers objectAtIndex:i];
363 if ([[[vc windowController] window] isEqual:keyWindow])
370 MMVimController *vc = [vimControllers objectAtIndex:i];
371 [[vc windowController] showWindow:self];
375 - (IBAction)selectPreviousWindow:(id)sender
377 unsigned i, count = [vimControllers count];
380 NSWindow *keyWindow = [NSApp keyWindow];
381 for (i = 0; i < count; ++i) {
382 MMVimController *vc = [vimControllers objectAtIndex:i];
383 if ([[[vc windowController] window] isEqual:keyWindow])
393 MMVimController *vc = [vimControllers objectAtIndex:i];
394 [[vc windowController] showWindow:self];
398 - (IBAction)fontSizeUp:(id)sender
400 [[NSFontManager sharedFontManager] modifyFont:
401 [NSNumber numberWithInt:NSSizeUpFontAction]];
404 - (IBAction)fontSizeDown:(id)sender
406 [[NSFontManager sharedFontManager] modifyFont:
407 [NSNumber numberWithInt:NSSizeDownFontAction]];
410 - (byref id <MMFrontendProtocol>)
411 connectBackend:(byref in id <MMBackendProtocol>)backend
414 //NSLog(@"Frontend got connection request from backend...adding new "
415 // "MMVimController");
417 [(NSDistantObject*)backend
418 setProtocolForProxy:@protocol(MMBackendProtocol)];
420 MMVimController *vc = [[[MMVimController alloc]
421 initWithBackend:backend pid:pid] autorelease];
423 if (![vimControllers count]) {
424 // The first window autosaves its position. (The autosaving features
425 // of Cocoa are not used because we need more control over what is
426 // autosaved and when it is restored.)
427 [[vc windowController] setWindowAutosaveKey:MMTopLeftPointKey];
430 [vimControllers addObject:vc];
432 // HACK! MacVim does not get activated if it is launched from the
433 // terminal, so we forcibly activate here unless it is an untitled window
434 // opening (i.e. MacVim was opened from the Finder). Untitled windows are
435 // treated differently, else MacVim would steal the focus if another app
436 // was activated while the untitled window was loading.
437 if (!untitledWindowOpening)
438 [NSApp activateIgnoringOtherApps:YES];
440 untitledWindowOpening = NO;
445 - (NSArray *)serverList
447 NSMutableArray *array = [NSMutableArray array];
449 unsigned i, count = [vimControllers count];
450 for (i = 0; i < count; ++i) {
451 MMVimController *controller = [vimControllers objectAtIndex:i];
452 if ([controller serverName])
453 [array addObject:[controller serverName]];
459 @end // MMAppController
464 @implementation MMAppController (MMServices)
466 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
467 error:(NSString **)error
469 if (![[pboard types] containsObject:NSStringPboardType]) {
470 NSLog(@"WARNING: Pasteboard contains no object of type "
471 "NSStringPboardType");
475 MMVimController *vc = [self topmostVimController];
477 // Open a new tab first, since dropString: does not do this.
478 [vc sendMessage:AddNewTabMsgID data:nil];
479 [vc dropString:[pboard stringForType:NSStringPboardType]];
481 // NOTE: There is no window to paste the selection into, so save the
482 // text, open a new window, and paste the text when the next window
483 // opens. (If this is called several times in a row, then all but the
484 // last call might be ignored.)
485 if (openSelectionString) [openSelectionString release];
486 openSelectionString = [[pboard stringForType:NSStringPboardType] copy];
488 [self newWindow:self];
492 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
493 error:(NSString **)error
495 if (![[pboard types] containsObject:NSStringPboardType]) {
496 NSLog(@"WARNING: Pasteboard contains no object of type "
497 "NSStringPboardType");
501 // TODO: Parse multiple filenames and create array with names.
502 NSString *string = [pboard stringForType:NSStringPboardType];
503 string = [string stringByTrimmingCharactersInSet:
504 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
505 string = [string stringByStandardizingPath];
507 MMVimController *vc = nil;
508 if (userData && [userData isEqual:@"Tab"])
509 vc = [self topmostVimController];
512 [vc dropFiles:[NSArray arrayWithObject:string]];
514 [self application:NSApp openFiles:[NSArray arrayWithObject:string]];
518 @end // MMAppController (MMServices)
523 @implementation MMAppController (Private)
525 - (MMVimController *)keyVimController
527 NSWindow *keyWindow = [NSApp keyWindow];
529 unsigned i, count = [vimControllers count];
530 for (i = 0; i < count; ++i) {
531 MMVimController *vc = [vimControllers objectAtIndex:i];
532 if ([[[vc windowController] window] isEqual:keyWindow])
540 - (MMVimController *)topmostVimController
542 NSArray *windows = [NSApp orderedWindows];
543 if ([windows count] > 0) {
544 NSWindow *window = [windows objectAtIndex:0];
545 unsigned i, count = [vimControllers count];
546 for (i = 0; i < count; ++i) {
547 MMVimController *vc = [vimControllers objectAtIndex:i];
548 if ([[[vc windowController] window] isEqual:window])
556 - (void)launchVimProcessWithArguments:(NSArray *)args
558 NSString *path = [[NSBundle mainBundle] pathForAuxiliaryExecutable:@"Vim"];
560 NSLog(@"ERROR: Vim executable could not be found inside app bundle!");
564 NSMutableString *execArg = [NSMutableString
565 stringWithFormat:@"exec \"%@\" -g", path];
567 // Append all arguments while making sure that arguments containing
568 // spaces are enclosed in quotes.
569 NSCharacterSet *space = [NSCharacterSet whitespaceCharacterSet];
570 unsigned i, count = [args count];
572 for (i = 0; i < count; ++i) {
573 NSString *arg = [args objectAtIndex:i];
574 if (NSNotFound != [arg rangeOfCharacterFromSet:space].location)
575 [execArg appendFormat:@" \"%@\"", arg];
577 [execArg appendFormat:@" %@", arg];
581 // Launch the process with a login shell so that users environment settings
582 // get sourced. This does not always happen when MacVim is started.
583 NSArray *shellArgs = [NSArray arrayWithObjects:@"-l", @"-c", execArg, nil];
584 NSString *shell = [[[NSProcessInfo processInfo] environment]
585 objectForKey:@"SHELL"];
589 //NSLog(@"Launching: %@ args: %@", shell, shellArgs);
590 [NSTask launchedTaskWithLaunchPath:shell arguments:shellArgs];
593 @end // MMAppController (Private)
598 @implementation NSMenu (MMExtras)
600 - (void)recurseSetAutoenablesItems:(BOOL)on
602 [self setAutoenablesItems:on];
604 int i, count = [self numberOfItems];
605 for (i = 0; i < count; ++i) {
606 NSMenuItem *item = [self itemAtIndex:i];
607 [item setEnabled:YES];
608 NSMenu *submenu = [item submenu];
610 [submenu recurseSetAutoenablesItems:on];
615 @end // NSMenu (MMExtras)
620 @implementation NSNumber (MMExtras)
623 return [self intValue];
625 @end // NSNumber (MMExtras)