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;
21 // Timeout used when the app should terminate.
22 static NSTimeInterval MMTerminateTimeout = 3;
26 @interface MMAppController (MMServices)
27 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
28 error:(NSString **)error;
29 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
30 error:(NSString **)error;
34 @interface MMAppController (Private)
35 - (MMVimController *)keyVimController;
36 - (MMVimController *)topmostVimController;
39 @interface NSMenu (MMExtras)
40 - (void)recurseSetAutoenablesItems:(BOOL)on;
43 @interface NSNumber (MMExtras)
49 @implementation MMAppController
53 NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
54 [NSNumber numberWithBool:NO], MMNoWindowKey,
55 [NSNumber numberWithInt:64], MMTabMinWidthKey,
56 [NSNumber numberWithInt:6*64], MMTabMaxWidthKey,
57 [NSNumber numberWithInt:132], MMTabOptimumWidthKey,
58 [NSNumber numberWithInt:2], MMTextInsetLeftKey,
59 [NSNumber numberWithInt:1], MMTextInsetRightKey,
60 [NSNumber numberWithInt:1], MMTextInsetTopKey,
61 [NSNumber numberWithInt:1], MMTextInsetBottomKey,
62 [NSNumber numberWithBool:NO], MMTerminateAfterLastWindowClosedKey,
63 @"MMTypesetter", MMTypesetterKey,
64 [NSNumber numberWithFloat:1], MMCellWidthMultiplierKey,
65 [NSNumber numberWithFloat:-1], MMBaselineOffsetKey,
66 [NSNumber numberWithBool:YES], MMTranslateCtrlClickKey,
67 [NSNumber numberWithBool:NO], MMOpenFilesInTabsKey,
70 [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
72 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
73 [NSApp registerServicesMenuSendTypes:types returnTypes:types];
78 if ((self = [super init])) {
79 fontContainerRef = loadFonts();
81 vimControllers = [NSMutableArray new];
83 // NOTE! If the name of the connection changes here it must also be
84 // updated in MMBackend.m.
85 NSConnection *connection = [NSConnection defaultConnection];
86 NSString *name = [NSString stringWithFormat:@"%@-connection",
87 [[NSBundle mainBundle] bundleIdentifier]];
88 //NSLog(@"Registering connection with name '%@'", name);
89 if ([connection registerName:name]) {
90 [connection setRequestTimeout:MMRequestTimeout];
91 [connection setReplyTimeout:MMReplyTimeout];
92 [connection setRootObject:self];
94 // NOTE: When the user is resizing the window the AppKit puts the
95 // run loop in event tracking mode. Unless the connection listens
96 // to request in this mode, live resizing won't work.
97 [connection addRequestMode:NSEventTrackingRunLoopMode];
99 NSLog(@"WARNING: Failed to register connection with name '%@'",
109 //NSLog(@"MMAppController dealloc");
111 [vimControllers release];
112 [openSelectionString release];
117 - (void)applicationDidFinishLaunching:(NSNotification *)notification
119 [NSApp setServicesProvider:self];
122 - (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
124 // NOTE! This way it possible to start the app with the command-line
125 // argument '-nowindow yes' and no window will be opened by default.
126 untitledWindowOpening =
127 ![[NSUserDefaults standardUserDefaults] boolForKey:MMNoWindowKey];
128 return untitledWindowOpening;
131 - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
133 //NSLog(@"%s NSapp=%@ theApp=%@", _cmd, NSApp, sender);
135 [self newWindow:self];
139 - (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
141 NSString *firstMissingFile = nil;
142 NSMutableArray *files = [NSMutableArray array];
143 int i, count = [filenames count];
144 for (i = 0; i < count; ++i) {
145 NSString *name = [filenames objectAtIndex:i];
146 if ([[NSFileManager defaultManager] fileExistsAtPath:name]) {
147 [files addObject:name];
148 } else if (!firstMissingFile) {
149 firstMissingFile = name;
153 if (firstMissingFile) {
154 NSAlert *alert = [[NSAlert alloc] init];
155 [alert addButtonWithTitle:@"OK"];
158 if ([files count] >= count-1) {
159 [alert setMessageText:@"File not found"];
160 text = [NSString stringWithFormat:@"Could not open file with "
161 "name %@.", firstMissingFile];
163 [alert setMessageText:@"Multiple files not found"];
164 text = [NSString stringWithFormat:@"Could not open file with "
165 "name %@, and %d other files.", firstMissingFile,
166 count-[files count]-1];
169 [alert setInformativeText:text];
170 [alert setAlertStyle:NSWarningAlertStyle];
175 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
180 BOOL openInTabs = [[NSUserDefaults standardUserDefaults]
181 boolForKey:MMOpenFilesInTabsKey];
183 if (openInTabs && (vc = [self topmostVimController])) {
184 [vc dropFiles:files];
186 NSMutableArray *args = [NSMutableArray arrayWithObjects:
188 [args addObjectsFromArray:files];
190 NSString *path = [[NSBundle mainBundle]
191 pathForAuxiliaryExecutable:@"Vim"];
193 NSLog(@"ERROR: Vim executable could not be found inside app "
195 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
199 [NSTask launchedTaskWithLaunchPath:path arguments:args];
202 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
203 // NSApplicationDelegateReplySuccess = 0,
204 // NSApplicationDelegateReplyCancel = 1,
205 // NSApplicationDelegateReplyFailure = 2
208 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
210 return [[NSUserDefaults standardUserDefaults]
211 boolForKey:MMTerminateAfterLastWindowClosedKey];
214 - (NSApplicationTerminateReply)applicationShouldTerminate:
215 (NSApplication *)sender
217 int reply = NSTerminateNow;
218 BOOL modifiedBuffers = NO;
219 BOOL notResponding = NO;
221 // Go through vim controllers, checking for modified buffers. If a process
222 // is not responding then note this as well.
223 unsigned i, count = [vimControllers count];
224 for (i = 0; i < count; ++i) {
225 MMVimController *controller = [vimControllers objectAtIndex:i];
226 id proxy = [controller backendProxy];
227 NSConnection *connection = [proxy connectionForProxy];
229 NSTimeInterval req = [connection requestTimeout];
230 NSTimeInterval rep = [connection replyTimeout];
231 [connection setRequestTimeout:MMTerminateTimeout];
232 [connection setReplyTimeout:MMTerminateTimeout];
235 if ([proxy checkForModifiedBuffers])
236 modifiedBuffers = YES;
238 @catch (NSException *e) {
239 NSLog(@"WARNING: Got exception while waiting for "
240 "checkForModifiedBuffers: \"%@\"", e);
244 [connection setRequestTimeout:req];
245 [connection setReplyTimeout:rep];
246 if (modifiedBuffers || notResponding)
252 if (modifiedBuffers || notResponding) {
253 NSAlert *alert = [[NSAlert alloc] init];
254 [alert addButtonWithTitle:@"Quit"];
255 [alert addButtonWithTitle:@"Cancel"];
256 if (modifiedBuffers) {
257 [alert setMessageText:@"Quit without saving?"];
258 [alert setInformativeText:@"There are modified buffers, "
259 "if you quit now all changes will be lost. Quit anyway?"];
261 [alert setMessageText:@"Force Quit?"];
262 [alert setInformativeText:@"At least one Vim process is not "
263 "responding, if you quit now any changes you have made "
264 "will be lost. Quit anyway?"];
266 [alert setAlertStyle:NSWarningAlertStyle];
268 if ([alert runModal] != NSAlertFirstButtonReturn) {
269 reply = NSTerminateCancel;
278 - (void)applicationWillTerminate:(NSNotification *)aNotification
280 // This will invalidate all connections (since they were spawned from the
281 // default connection).
282 [[NSConnection defaultConnection] invalidate];
284 // Send a SIGINT to all running Vim processes, so that they are sure to
285 // receive the connectionDidDie: notification (a process has to checking
286 // the run-loop for this to happen).
287 unsigned i, count = [vimControllers count];
288 for (i = 0; i < count; ++i) {
289 MMVimController *controller = [vimControllers objectAtIndex:i];
290 int pid = [controller pid];
295 if (fontContainerRef) {
296 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
297 fontContainerRef = 0;
300 // TODO: Is this a correct way of releasing the MMAppController?
301 // (It doesn't seem like dealloc is ever called.)
302 [NSApp setDelegate:nil];
306 - (void)removeVimController:(id)controller
308 //NSLog(@"%s%@", _cmd, controller);
310 [[controller windowController] close];
312 [vimControllers removeObject:controller];
314 if (![vimControllers count]) {
315 // Turn on autoenabling of menus (because no Vim is open to handle it),
316 // but do not touch the MacVim menu. Note that the menus must be
317 // enabled first otherwise autoenabling does not work.
318 NSMenu *mainMenu = [NSApp mainMenu];
319 int i, count = [mainMenu numberOfItems];
320 for (i = 1; i < count; ++i) {
321 NSMenuItem *item = [mainMenu itemAtIndex:i];
322 [item setEnabled:YES];
323 [[item submenu] recurseSetAutoenablesItems:YES];
328 - (void)windowControllerWillOpen:(MMWindowController *)windowController
330 NSPoint topLeft = NSZeroPoint;
331 NSWindow *keyWin = [NSApp keyWindow];
332 NSWindow *win = [windowController window];
336 // If there is a key window, cascade from it, otherwise use the autosaved
337 // window position (if any).
339 NSRect frame = [keyWin frame];
340 topLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
342 NSString *topLeftString = [[NSUserDefaults standardUserDefaults]
343 stringForKey:MMTopLeftPointKey];
345 topLeft = NSPointFromString(topLeftString);
348 if (!NSEqualPoints(topLeft, NSZeroPoint)) {
350 topLeft = [win cascadeTopLeftFromPoint:topLeft];
352 [win setFrameTopLeftPoint:topLeft];
355 if (openSelectionString) {
356 // There is some text to paste into this window as a result of the
357 // services menu "Open selection ..." being used.
358 [[windowController vimController] dropString:openSelectionString];
359 [openSelectionString release];
360 openSelectionString = nil;
364 - (IBAction)newWindow:(id)sender
366 NSMutableArray *args = [NSMutableArray arrayWithObject:@"-g"];
367 NSString *path = [[NSBundle mainBundle]
368 pathForAuxiliaryExecutable:@"Vim"];
370 NSLog(@"ERROR: Vim executable could not be found inside app bundle!");
375 //NSLog(@"Launching a new VimTask...");
376 [NSTask launchedTaskWithLaunchPath:path arguments:args];
379 - (IBAction)selectNextWindow:(id)sender
381 unsigned i, count = [vimControllers count];
384 NSWindow *keyWindow = [NSApp keyWindow];
385 for (i = 0; i < count; ++i) {
386 MMVimController *vc = [vimControllers objectAtIndex:i];
387 if ([[[vc windowController] window] isEqual:keyWindow])
394 MMVimController *vc = [vimControllers objectAtIndex:i];
395 [[vc windowController] showWindow:self];
399 - (IBAction)selectPreviousWindow:(id)sender
401 unsigned i, count = [vimControllers count];
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])
417 MMVimController *vc = [vimControllers objectAtIndex:i];
418 [[vc windowController] showWindow:self];
422 - (IBAction)fontSizeUp:(id)sender
424 [[NSFontManager sharedFontManager] modifyFont:
425 [NSNumber numberWithInt:NSSizeUpFontAction]];
428 - (IBAction)fontSizeDown:(id)sender
430 [[NSFontManager sharedFontManager] modifyFont:
431 [NSNumber numberWithInt:NSSizeDownFontAction]];
434 - (byref id <MMFrontendProtocol>)
435 connectBackend:(byref in id <MMBackendProtocol>)backend
438 //NSLog(@"Frontend got connection request from backend...adding new "
439 // "MMVimController");
441 [(NSDistantObject*)backend
442 setProtocolForProxy:@protocol(MMBackendProtocol)];
444 MMVimController *vc = [[[MMVimController alloc]
445 initWithBackend:backend pid:pid] autorelease];
447 if (![vimControllers count]) {
448 // The first window autosaves its position. (The autosaving features
449 // of Cocoa are not used because we need more control over what is
450 // autosaved and when it is restored.)
451 [[vc windowController] setWindowAutosaveKey:MMTopLeftPointKey];
454 [vimControllers addObject:vc];
456 // HACK! MacVim does not get activated if it is launched from the
457 // terminal, so we forcibly activate here unless it is an untitled window
458 // opening (i.e. MacVim was opened from the Finder). Untitled windows are
459 // treated differently, else MacVim would steal the focus if another app
460 // was activated while the untitled window was loading.
461 if (!untitledWindowOpening)
462 [NSApp activateIgnoringOtherApps:YES];
464 untitledWindowOpening = NO;
469 - (NSArray *)serverList
471 NSMutableArray *array = [NSMutableArray array];
473 unsigned i, count = [vimControllers count];
474 for (i = 0; i < count; ++i) {
475 MMVimController *controller = [vimControllers objectAtIndex:i];
476 if ([controller serverName])
477 [array addObject:[controller serverName]];
483 @end // MMAppController
488 @implementation MMAppController (MMServices)
490 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
491 error:(NSString **)error
493 if (![[pboard types] containsObject:NSStringPboardType]) {
494 NSLog(@"WARNING: Pasteboard contains no object of type "
495 "NSStringPboardType");
499 MMVimController *vc = [self topmostVimController];
501 // Open a new tab first, since dropString: does not do this.
502 [vc sendMessage:AddNewTabMsgID data:nil];
503 [vc dropString:[pboard stringForType:NSStringPboardType]];
505 // NOTE: There is no window to paste the selection into, so save the
506 // text, open a new window, and paste the text when the next window
507 // opens. (If this is called several times in a row, then all but the
508 // last call might be ignored.)
509 if (openSelectionString) [openSelectionString release];
510 openSelectionString = [[pboard stringForType:NSStringPboardType] copy];
512 [self newWindow:self];
516 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
517 error:(NSString **)error
519 if (![[pboard types] containsObject:NSStringPboardType]) {
520 NSLog(@"WARNING: Pasteboard contains no object of type "
521 "NSStringPboardType");
525 // TODO: Parse multiple filenames and create array with names.
526 NSString *string = [pboard stringForType:NSStringPboardType];
527 string = [string stringByTrimmingCharactersInSet:
528 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
529 string = [string stringByStandardizingPath];
531 MMVimController *vc = nil;
532 if (userData && [userData isEqual:@"Tab"])
533 vc = [self topmostVimController];
536 [vc dropFiles:[NSArray arrayWithObject:string]];
538 [self application:NSApp openFiles:[NSArray arrayWithObject:string]];
542 @end // MMAppController (MMServices)
547 @implementation MMAppController (Private)
549 - (MMVimController *)keyVimController
551 NSWindow *keyWindow = [NSApp keyWindow];
553 unsigned i, count = [vimControllers count];
554 for (i = 0; i < count; ++i) {
555 MMVimController *vc = [vimControllers objectAtIndex:i];
556 if ([[[vc windowController] window] isEqual:keyWindow])
564 - (MMVimController *)topmostVimController
566 NSArray *windows = [NSApp orderedWindows];
567 if ([windows count] > 0) {
568 NSWindow *window = [windows objectAtIndex:0];
569 unsigned i, count = [vimControllers count];
570 for (i = 0; i < count; ++i) {
571 MMVimController *vc = [vimControllers objectAtIndex:i];
572 if ([[[vc windowController] window] isEqual:window])
585 @implementation NSMenu (MMExtras)
587 - (void)recurseSetAutoenablesItems:(BOOL)on
589 [self setAutoenablesItems:on];
591 int i, count = [self numberOfItems];
592 for (i = 0; i < count; ++i) {
593 NSMenuItem *item = [self itemAtIndex:i];
594 [item setEnabled:YES];
595 NSMenu *submenu = [item submenu];
597 [submenu recurseSetAutoenablesItems:on];
602 @end // NSMenu (MMExtras)
607 @implementation NSNumber (MMExtras)
610 return [self intValue];
612 @end // NSNumber (MMExtras)