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 vimControllers = [NSMutableArray new];
77 // NOTE! If the name of the connection changes here it must also be
78 // updated in MMBackend.m.
79 NSConnection *connection = [NSConnection defaultConnection];
80 NSString *name = [NSString stringWithFormat:@"%@-connection",
81 [[NSBundle mainBundle] bundleIdentifier]];
82 //NSLog(@"Registering connection with name '%@'", name);
83 if ([connection registerName:name]) {
84 [connection setRequestTimeout:MMRequestTimeout];
85 [connection setReplyTimeout:MMReplyTimeout];
86 [connection setRootObject:self];
88 // NOTE: When the user is resizing the window the AppKit puts the
89 // run loop in event tracking mode. Unless the connection listens
90 // to request in this mode, live resizing won't work.
91 [connection addRequestMode:NSEventTrackingRunLoopMode];
93 NSLog(@"WARNING: Failed to register connection with name '%@'",
103 //NSLog(@"MMAppController dealloc");
105 [vimControllers release];
106 [openSelectionString release];
111 - (void)applicationDidFinishLaunching:(NSNotification *)notification
113 [NSApp setServicesProvider:self];
116 - (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
118 // NOTE! This way it possible to start the app with the command-line
119 // argument '-nowindow yes' and no window will be opened by default.
120 return ![[NSUserDefaults standardUserDefaults] boolForKey:MMNoWindowKey];
123 - (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
125 //NSLog(@"%s NSapp=%@ theApp=%@", _cmd, NSApp, sender);
127 [self newVimWindow:self];
131 - (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
133 NSString *firstMissingFile = nil;
134 NSMutableArray *files = [NSMutableArray array];
135 int i, count = [filenames count];
136 for (i = 0; i < count; ++i) {
137 NSString *name = [filenames objectAtIndex:i];
138 if ([NSFileHandle fileHandleForReadingAtPath:name]) {
139 [files addObject:name];
140 } else if (!firstMissingFile) {
141 firstMissingFile = name;
145 if (firstMissingFile) {
146 NSAlert *alert = [[NSAlert alloc] init];
147 [alert addButtonWithTitle:@"OK"];
150 if ([files count] >= count-1) {
151 [alert setMessageText:@"File not found"];
152 text = [NSString stringWithFormat:@"Could not open file with "
153 "name %@.", firstMissingFile];
155 [alert setMessageText:@"Multiple files not found"];
156 text = [NSString stringWithFormat:@"Could not open file with "
157 "name %@, and %d other files.", firstMissingFile,
158 count-[files count]-1];
161 [alert setInformativeText:text];
162 [alert setAlertStyle:NSWarningAlertStyle];
167 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
172 BOOL openInTabs = [[NSUserDefaults standardUserDefaults]
173 boolForKey:MMOpenFilesInTabsKey];
175 if (openInTabs && (vc = [self topmostVimController])) {
176 [vc dropFiles:files];
178 NSMutableArray *args = [NSMutableArray arrayWithObjects:
180 [args addObjectsFromArray:files];
182 NSString *path = [[NSBundle mainBundle]
183 pathForAuxiliaryExecutable:@"Vim"];
185 NSLog(@"ERROR: Vim executable could not be found inside app "
187 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
191 [NSTask launchedTaskWithLaunchPath:path arguments:args];
194 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
195 // NSApplicationDelegateReplySuccess = 0,
196 // NSApplicationDelegateReplyCancel = 1,
197 // NSApplicationDelegateReplyFailure = 2
200 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
202 return [[NSUserDefaults standardUserDefaults]
203 boolForKey:MMTerminateAfterLastWindowClosedKey];
206 - (NSApplicationTerminateReply)applicationShouldTerminate:
207 (NSApplication *)sender
209 int reply = NSTerminateNow;
210 BOOL modifiedBuffers = NO;
211 BOOL notResponding = NO;
213 // Go through vim controllers, checking for modified buffers. If a process
214 // is not responding then note this as well.
215 unsigned i, count = [vimControllers count];
216 for (i = 0; i < count; ++i) {
217 MMVimController *controller = [vimControllers objectAtIndex:i];
218 id proxy = [controller backendProxy];
219 NSConnection *connection = [proxy connectionForProxy];
221 NSTimeInterval req = [connection requestTimeout];
222 NSTimeInterval rep = [connection replyTimeout];
223 [connection setRequestTimeout:MMTerminateTimeout];
224 [connection setReplyTimeout:MMTerminateTimeout];
227 if ([proxy checkForModifiedBuffers])
228 modifiedBuffers = YES;
230 @catch (NSException *e) {
231 NSLog(@"WARNING: Got exception while waiting for "
232 "checkForModifiedBuffers: \"%@\"", e);
236 [connection setRequestTimeout:req];
237 [connection setReplyTimeout:rep];
238 if (modifiedBuffers || notResponding)
244 if (modifiedBuffers || notResponding) {
245 NSAlert *alert = [[NSAlert alloc] init];
246 [alert addButtonWithTitle:@"Quit"];
247 [alert addButtonWithTitle:@"Cancel"];
248 if (modifiedBuffers) {
249 [alert setMessageText:@"Quit without saving?"];
250 [alert setInformativeText:@"There are modified buffers, "
251 "if you quit now all changes will be lost. Quit anyway?"];
253 [alert setMessageText:@"Force Quit?"];
254 [alert setInformativeText:@"At least one Vim process is not "
255 "responding, if you quit now any changes you have made "
256 "will be lost. Quit anyway?"];
258 [alert setAlertStyle:NSWarningAlertStyle];
260 if ([alert runModal] != NSAlertFirstButtonReturn) {
261 reply = NSTerminateCancel;
270 - (void)applicationWillTerminate:(NSNotification *)aNotification
272 // Send a SIGINT to all running Vim processes, so that they are sure to
273 // receive the connectionDidDie: notification (a process has to checking
274 // the run-loop for this to happen).
275 unsigned i, count = [vimControllers count];
276 for (i = 0; i < count; ++i) {
277 MMVimController *controller = [vimControllers objectAtIndex:i];
278 int pid = [controller pid];
282 id proxy = [controller backendProxy];
283 NSConnection *connection = [proxy connectionForProxy];
285 [connection invalidate];
289 // NOTE! Is this a correct way of releasing the MMAppController?
290 [NSApp setDelegate:nil];
294 - (void)removeVimController:(id)controller
296 //NSLog(@"%s%@", _cmd, controller);
298 [[controller windowController] close];
300 [vimControllers removeObject:controller];
302 if (![vimControllers count]) {
303 // Turn on autoenabling of menus (because no Vim is open to handle it),
304 // but do not touch the MacVim menu.
305 NSMenu *mainMenu = [NSApp mainMenu];
306 int i, count = [mainMenu numberOfItems];
307 for (i = 1; i < count; ++i) {
308 NSMenu *submenu = [[mainMenu itemAtIndex:i] submenu];
309 [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)newVimWindow:(id)sender
352 NSMutableArray *args = [NSMutableArray arrayWithObject:@"-g"];
353 NSString *path = [[NSBundle mainBundle]
354 pathForAuxiliaryExecutable:@"Vim"];
356 NSLog(@"ERROR: Vim executable could not be found inside app bundle!");
361 //NSLog(@"Launching a new VimTask...");
362 [NSTask launchedTaskWithLaunchPath:path arguments:args];
365 - (IBAction)selectNextWindow:(id)sender
367 unsigned i, count = [vimControllers count];
370 NSWindow *keyWindow = [NSApp keyWindow];
371 for (i = 0; i < count; ++i) {
372 MMVimController *vc = [vimControllers objectAtIndex:i];
373 if ([[[vc windowController] window] isEqual:keyWindow])
380 MMVimController *vc = [vimControllers objectAtIndex:i];
381 [[vc windowController] showWindow:self];
385 - (IBAction)selectPreviousWindow:(id)sender
387 unsigned i, count = [vimControllers count];
390 NSWindow *keyWindow = [NSApp keyWindow];
391 for (i = 0; i < count; ++i) {
392 MMVimController *vc = [vimControllers objectAtIndex:i];
393 if ([[[vc windowController] window] isEqual:keyWindow])
403 MMVimController *vc = [vimControllers objectAtIndex:i];
404 [[vc windowController] showWindow:self];
408 - (byref id <MMFrontendProtocol>)
409 connectBackend:(byref in id <MMBackendProtocol>)backend
412 //NSLog(@"Frontend got connection request from backend...adding new "
413 // "MMVimController");
415 [(NSDistantObject*)backend
416 setProtocolForProxy:@protocol(MMBackendProtocol)];
418 MMVimController *vc = [[[MMVimController alloc]
419 initWithBackend:backend pid:pid] autorelease];
421 if (![vimControllers count]) {
422 // The first window autosaves its position. (The autosaving features
423 // of Cocoa are not used because we need more control over what is
424 // autosaved and when it is restored.)
425 [[vc windowController] setWindowAutosaveKey:MMTopLeftPointKey];
428 [vimControllers addObject:vc];
430 // HACK! MacVim does not get activated if it is launched from the
431 // terminal, so we forcibly activate here.
432 [NSApp activateIgnoringOtherApps:YES];
437 - (NSArray *)serverList
439 NSMutableArray *array = [NSMutableArray array];
441 unsigned i, count = [vimControllers count];
442 for (i = 0; i < count; ++i) {
443 MMVimController *controller = [vimControllers objectAtIndex:i];
445 id proxy = [controller backendProxy];
446 NSConnection *connection = [proxy connectionForProxy];
450 // Set low timeouts since we don't want this call to potentially lock
451 // up MacVim for a while.
452 NSTimeInterval req = [connection requestTimeout];
453 NSTimeInterval rep = [connection replyTimeout];
454 [connection setRequestTimeout:0];
455 [connection setReplyTimeout:.1];
458 NSString *eval = [proxy evaluateExpression:@"v:servername"];
460 [array addObject:eval];
463 @catch (NSException *e) {
464 NSLog(@"WARNING: Got exception while listing servers: \"%@\"", e);
467 [connection setRequestTimeout:req];
468 [connection setReplyTimeout:rep];
471 if ([controller serverName])
472 [array addObject:[controller serverName]];
479 @end // MMAppController
484 @implementation MMAppController (MMServices)
486 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
487 error:(NSString **)error
489 if (![[pboard types] containsObject:NSStringPboardType]) {
490 NSLog(@"WARNING: Pasteboard contains no object of type "
491 "NSStringPboardType");
495 MMVimController *vc = [self topmostVimController];
497 // Open a new tab first, since dropString: does not do this.
498 [vc sendMessage:AddNewTabMsgID data:nil wait:NO];
499 [vc dropString:[pboard stringForType:NSStringPboardType]];
501 // NOTE: There is no window to paste the selection into, so save the
502 // text, open a new window, and paste the text when the next window
503 // opens. (If this is called several times in a row, then all but the
504 // last call might be ignored.)
505 if (openSelectionString) [openSelectionString release];
506 openSelectionString = [[pboard stringForType:NSStringPboardType] copy];
508 [self newVimWindow:self];
512 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
513 error:(NSString **)error
515 if (![[pboard types] containsObject:NSStringPboardType]) {
516 NSLog(@"WARNING: Pasteboard contains no object of type "
517 "NSStringPboardType");
521 // TODO: Parse multiple filenames and create array with names.
522 NSString *string = [pboard stringForType:NSStringPboardType];
523 string = [string stringByTrimmingCharactersInSet:
524 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
525 string = [string stringByStandardizingPath];
527 MMVimController *vc = nil;
528 if (userData && [userData isEqual:@"Tab"])
529 vc = [self topmostVimController];
532 [vc dropFiles:[NSArray arrayWithObject:string]];
534 [self application:NSApp openFiles:[NSArray arrayWithObject:string]];
538 @end // MMAppController (MMServices)
543 @implementation MMAppController (Private)
545 - (MMVimController *)keyVimController
547 NSWindow *keyWindow = [NSApp keyWindow];
549 unsigned i, count = [vimControllers count];
550 for (i = 0; i < count; ++i) {
551 MMVimController *vc = [vimControllers objectAtIndex:i];
552 if ([[[vc windowController] window] isEqual:keyWindow])
560 - (MMVimController *)topmostVimController
562 NSArray *windows = [NSApp orderedWindows];
563 if ([windows count] > 0) {
564 NSWindow *window = [windows objectAtIndex:0];
565 unsigned i, count = [vimControllers count];
566 for (i = 0; i < count; ++i) {
567 MMVimController *vc = [vimControllers objectAtIndex:i];
568 if ([[[vc windowController] window] isEqual:window])
581 @implementation NSMenu (MMExtras)
583 - (void)recurseSetAutoenablesItems:(BOOL)on
585 [self setAutoenablesItems:on];
587 int i, count = [self numberOfItems];
588 for (i = 0; i < count; ++i) {
589 NSMenu *submenu = [[self itemAtIndex:i] submenu];
591 [submenu recurseSetAutoenablesItems:on];
596 @end // NSMenu (MMExtras)