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;
45 @implementation MMAppController
49 NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
50 [NSNumber numberWithBool:NO], MMNoWindowKey,
51 [NSNumber numberWithInt:64], MMTabMinWidthKey,
52 [NSNumber numberWithInt:6*64], MMTabMaxWidthKey,
53 [NSNumber numberWithInt:132], MMTabOptimumWidthKey,
54 [NSNumber numberWithInt:2], MMTextInsetLeftKey,
55 [NSNumber numberWithInt:1], MMTextInsetRightKey,
56 [NSNumber numberWithInt:1], MMTextInsetTopKey,
57 [NSNumber numberWithInt:1], MMTextInsetBottomKey,
58 [NSNumber numberWithBool:NO], MMTerminateAfterLastWindowClosedKey,
59 @"MMTypesetter", MMTypesetterKey,
60 [NSNumber numberWithFloat:1], MMCellWidthMultiplierKey,
61 [NSNumber numberWithFloat:-1], MMBaselineOffsetKey,
62 [NSNumber numberWithBool:YES], MMTranslateCtrlClickKey,
63 [NSNumber numberWithBool:NO], MMOpenFilesInTabsKey,
66 [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
68 NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
69 [NSApp registerServicesMenuSendTypes:types returnTypes:types];
74 if ((self = [super init])) {
75 fontContainerRef = loadFonts();
77 vimControllers = [NSMutableArray new];
79 // NOTE! If the name of the connection changes here it must also be
80 // updated in MMBackend.m.
81 NSConnection *connection = [NSConnection defaultConnection];
82 NSString *name = [NSString stringWithFormat:@"%@-connection",
83 [[NSBundle mainBundle] bundleIdentifier]];
84 //NSLog(@"Registering connection with name '%@'", name);
85 if ([connection registerName:name]) {
86 [connection setRequestTimeout:MMRequestTimeout];
87 [connection setReplyTimeout:MMReplyTimeout];
88 [connection setRootObject:self];
90 // NOTE: When the user is resizing the window the AppKit puts the
91 // run loop in event tracking mode. Unless the connection listens
92 // to request in this mode, live resizing won't work.
93 [connection addRequestMode:NSEventTrackingRunLoopMode];
95 NSLog(@"WARNING: Failed to register connection with name '%@'",
105 //NSLog(@"MMAppController dealloc");
107 [vimControllers release];
108 [openSelectionString release];
113 - (void)applicationDidFinishLaunching:(NSNotification *)notification
115 [NSApp setServicesProvider:self];
118 - (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
120 // NOTE! This way it possible to start the app with the command-line
121 // argument '-nowindow yes' and no window will be opened by default.
122 untitledWindowOpening =
123 ![[NSUserDefaults standardUserDefaults] boolForKey:MMNoWindowKey];
124 return untitledWindowOpening;
127 - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
129 //NSLog(@"%s NSapp=%@ theApp=%@", _cmd, NSApp, sender);
131 [self newWindow:self];
135 - (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
137 NSString *firstMissingFile = nil;
138 NSMutableArray *files = [NSMutableArray array];
139 int i, count = [filenames count];
140 for (i = 0; i < count; ++i) {
141 NSString *name = [filenames objectAtIndex:i];
142 if ([[NSFileManager defaultManager] fileExistsAtPath:name]) {
143 [files addObject:name];
144 } else if (!firstMissingFile) {
145 firstMissingFile = name;
149 if (firstMissingFile) {
150 NSAlert *alert = [[NSAlert alloc] init];
151 [alert addButtonWithTitle:@"OK"];
154 if ([files count] >= count-1) {
155 [alert setMessageText:@"File not found"];
156 text = [NSString stringWithFormat:@"Could not open file with "
157 "name %@.", firstMissingFile];
159 [alert setMessageText:@"Multiple files not found"];
160 text = [NSString stringWithFormat:@"Could not open file with "
161 "name %@, and %d other files.", firstMissingFile,
162 count-[files count]-1];
165 [alert setInformativeText:text];
166 [alert setAlertStyle:NSWarningAlertStyle];
171 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
176 BOOL openInTabs = [[NSUserDefaults standardUserDefaults]
177 boolForKey:MMOpenFilesInTabsKey];
179 if (openInTabs && (vc = [self topmostVimController])) {
180 [vc dropFiles:files];
182 NSMutableArray *args = [NSMutableArray arrayWithObjects:
184 [args addObjectsFromArray:files];
186 NSString *path = [[NSBundle mainBundle]
187 pathForAuxiliaryExecutable:@"Vim"];
189 NSLog(@"ERROR: Vim executable could not be found inside app "
191 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
195 [NSTask launchedTaskWithLaunchPath:path arguments:args];
198 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
199 // NSApplicationDelegateReplySuccess = 0,
200 // NSApplicationDelegateReplyCancel = 1,
201 // NSApplicationDelegateReplyFailure = 2
204 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
206 return [[NSUserDefaults standardUserDefaults]
207 boolForKey:MMTerminateAfterLastWindowClosedKey];
210 - (NSApplicationTerminateReply)applicationShouldTerminate:
211 (NSApplication *)sender
213 int reply = NSTerminateNow;
214 BOOL modifiedBuffers = NO;
215 BOOL notResponding = NO;
217 // Go through vim controllers, checking for modified buffers. If a process
218 // is not responding then note this as well.
219 unsigned i, count = [vimControllers count];
220 for (i = 0; i < count; ++i) {
221 MMVimController *controller = [vimControllers objectAtIndex:i];
222 id proxy = [controller backendProxy];
223 NSConnection *connection = [proxy connectionForProxy];
225 NSTimeInterval req = [connection requestTimeout];
226 NSTimeInterval rep = [connection replyTimeout];
227 [connection setRequestTimeout:MMTerminateTimeout];
228 [connection setReplyTimeout:MMTerminateTimeout];
231 if ([proxy checkForModifiedBuffers])
232 modifiedBuffers = YES;
234 @catch (NSException *e) {
235 NSLog(@"WARNING: Got exception while waiting for "
236 "checkForModifiedBuffers: \"%@\"", e);
240 [connection setRequestTimeout:req];
241 [connection setReplyTimeout:rep];
242 if (modifiedBuffers || notResponding)
248 if (modifiedBuffers || notResponding) {
249 NSAlert *alert = [[NSAlert alloc] init];
250 [alert addButtonWithTitle:@"Quit"];
251 [alert addButtonWithTitle:@"Cancel"];
252 if (modifiedBuffers) {
253 [alert setMessageText:@"Quit without saving?"];
254 [alert setInformativeText:@"There are modified buffers, "
255 "if you quit now all changes will be lost. Quit anyway?"];
257 [alert setMessageText:@"Force Quit?"];
258 [alert setInformativeText:@"At least one Vim process is not "
259 "responding, if you quit now any changes you have made "
260 "will be lost. Quit anyway?"];
262 [alert setAlertStyle:NSWarningAlertStyle];
264 if ([alert runModal] != NSAlertFirstButtonReturn) {
265 reply = NSTerminateCancel;
274 - (void)applicationWillTerminate:(NSNotification *)aNotification
276 // Send a SIGINT to all running Vim processes, so that they are sure to
277 // receive the connectionDidDie: notification (a process has to checking
278 // the run-loop for this to happen).
279 unsigned i, count = [vimControllers count];
280 for (i = 0; i < count; ++i) {
281 MMVimController *controller = [vimControllers objectAtIndex:i];
282 int pid = [controller pid];
286 id proxy = [controller backendProxy];
287 NSConnection *connection = [proxy connectionForProxy];
289 [connection invalidate];
293 if (fontContainerRef) {
294 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
295 fontContainerRef = 0;
298 // TODO: Is this a correct way of releasing the MMAppController?
299 // (It doesn't seem like dealloc is ever called.)
300 [NSApp setDelegate:nil];
304 - (void)removeVimController:(id)controller
306 //NSLog(@"%s%@", _cmd, controller);
308 [[controller windowController] close];
310 [vimControllers removeObject:controller];
312 if (![vimControllers count]) {
313 // Turn on autoenabling of menus (because no Vim is open to handle it),
314 // but do not touch the MacVim menu.
315 NSMenu *mainMenu = [NSApp mainMenu];
316 int i, count = [mainMenu numberOfItems];
317 for (i = 1; i < count; ++i) {
318 NSMenu *submenu = [[mainMenu itemAtIndex:i] submenu];
319 [submenu recurseSetAutoenablesItems:YES];
324 - (void)windowControllerWillOpen:(MMWindowController *)windowController
326 NSPoint topLeft = NSZeroPoint;
327 NSWindow *keyWin = [NSApp keyWindow];
328 NSWindow *win = [windowController window];
332 // If there is a key window, cascade from it, otherwise use the autosaved
333 // window position (if any).
335 NSRect frame = [keyWin frame];
336 topLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
338 NSString *topLeftString = [[NSUserDefaults standardUserDefaults]
339 stringForKey:MMTopLeftPointKey];
341 topLeft = NSPointFromString(topLeftString);
344 if (!NSEqualPoints(topLeft, NSZeroPoint)) {
346 topLeft = [win cascadeTopLeftFromPoint:topLeft];
348 [win setFrameTopLeftPoint:topLeft];
351 if (openSelectionString) {
352 // There is some text to paste into this window as a result of the
353 // services menu "Open selection ..." being used.
354 [[windowController vimController] dropString:openSelectionString];
355 [openSelectionString release];
356 openSelectionString = nil;
360 - (IBAction)newWindow:(id)sender
362 NSMutableArray *args = [NSMutableArray arrayWithObject:@"-g"];
363 NSString *path = [[NSBundle mainBundle]
364 pathForAuxiliaryExecutable:@"Vim"];
366 NSLog(@"ERROR: Vim executable could not be found inside app bundle!");
371 //NSLog(@"Launching a new VimTask...");
372 [NSTask launchedTaskWithLaunchPath:path arguments:args];
375 - (IBAction)selectNextWindow:(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])
390 MMVimController *vc = [vimControllers objectAtIndex:i];
391 [[vc windowController] showWindow:self];
395 - (IBAction)selectPreviousWindow:(id)sender
397 unsigned i, count = [vimControllers count];
400 NSWindow *keyWindow = [NSApp keyWindow];
401 for (i = 0; i < count; ++i) {
402 MMVimController *vc = [vimControllers objectAtIndex:i];
403 if ([[[vc windowController] window] isEqual:keyWindow])
413 MMVimController *vc = [vimControllers objectAtIndex:i];
414 [[vc windowController] showWindow:self];
418 - (byref id <MMFrontendProtocol>)
419 connectBackend:(byref in id <MMBackendProtocol>)backend
422 //NSLog(@"Frontend got connection request from backend...adding new "
423 // "MMVimController");
425 [(NSDistantObject*)backend
426 setProtocolForProxy:@protocol(MMBackendProtocol)];
428 MMVimController *vc = [[[MMVimController alloc]
429 initWithBackend:backend pid:pid] autorelease];
431 if (![vimControllers count]) {
432 // The first window autosaves its position. (The autosaving features
433 // of Cocoa are not used because we need more control over what is
434 // autosaved and when it is restored.)
435 [[vc windowController] setWindowAutosaveKey:MMTopLeftPointKey];
438 [vimControllers addObject:vc];
440 // HACK! MacVim does not get activated if it is launched from the
441 // terminal, so we forcibly activate here unless it is an untitled window
442 // opening (i.e. MacVim was opened from the Finder). Untitled windows are
443 // treated differently, else MacVim would steal the focus if another app
444 // was activated while the untitled window was loading.
445 if (!untitledWindowOpening)
446 [NSApp activateIgnoringOtherApps:YES];
448 untitledWindowOpening = NO;
453 - (NSArray *)serverList
455 NSMutableArray *array = [NSMutableArray array];
457 unsigned i, count = [vimControllers count];
458 for (i = 0; i < count; ++i) {
459 MMVimController *controller = [vimControllers objectAtIndex:i];
460 if ([controller serverName])
461 [array addObject:[controller serverName]];
467 @end // MMAppController
472 @implementation MMAppController (MMServices)
474 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
475 error:(NSString **)error
477 if (![[pboard types] containsObject:NSStringPboardType]) {
478 NSLog(@"WARNING: Pasteboard contains no object of type "
479 "NSStringPboardType");
483 MMVimController *vc = [self topmostVimController];
485 // Open a new tab first, since dropString: does not do this.
486 [vc sendMessage:AddNewTabMsgID data:nil];
487 [vc dropString:[pboard stringForType:NSStringPboardType]];
489 // NOTE: There is no window to paste the selection into, so save the
490 // text, open a new window, and paste the text when the next window
491 // opens. (If this is called several times in a row, then all but the
492 // last call might be ignored.)
493 if (openSelectionString) [openSelectionString release];
494 openSelectionString = [[pboard stringForType:NSStringPboardType] copy];
496 [self newWindow:self];
500 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
501 error:(NSString **)error
503 if (![[pboard types] containsObject:NSStringPboardType]) {
504 NSLog(@"WARNING: Pasteboard contains no object of type "
505 "NSStringPboardType");
509 // TODO: Parse multiple filenames and create array with names.
510 NSString *string = [pboard stringForType:NSStringPboardType];
511 string = [string stringByTrimmingCharactersInSet:
512 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
513 string = [string stringByStandardizingPath];
515 MMVimController *vc = nil;
516 if (userData && [userData isEqual:@"Tab"])
517 vc = [self topmostVimController];
520 [vc dropFiles:[NSArray arrayWithObject:string]];
522 [self application:NSApp openFiles:[NSArray arrayWithObject:string]];
526 @end // MMAppController (MMServices)
531 @implementation MMAppController (Private)
533 - (MMVimController *)keyVimController
535 NSWindow *keyWindow = [NSApp keyWindow];
537 unsigned i, count = [vimControllers count];
538 for (i = 0; i < count; ++i) {
539 MMVimController *vc = [vimControllers objectAtIndex:i];
540 if ([[[vc windowController] window] isEqual:keyWindow])
548 - (MMVimController *)topmostVimController
550 NSArray *windows = [NSApp orderedWindows];
551 if ([windows count] > 0) {
552 NSWindow *window = [windows objectAtIndex:0];
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:window])
569 @implementation NSMenu (MMExtras)
571 - (void)recurseSetAutoenablesItems:(BOOL)on
573 [self setAutoenablesItems:on];
575 int i, count = [self numberOfItems];
576 for (i = 0; i < count; ++i) {
577 NSMenu *submenu = [[self itemAtIndex:i] submenu];
579 [submenu recurseSetAutoenablesItems:on];
584 @end // NSMenu (MMExtras)