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];
179 BOOL openInTabs = [[NSUserDefaults standardUserDefaults]
180 boolForKey:MMOpenFilesInTabsKey];
182 if (openInTabs && (vc = [self topmostVimController])) {
183 [vc dropFiles:files];
185 NSMutableArray *args = [NSMutableArray arrayWithObject:@"-p"];
186 [args addObjectsFromArray:files];
187 [self launchVimProcessWithArguments:args];
190 [NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
191 // NSApplicationDelegateReplySuccess = 0,
192 // NSApplicationDelegateReplyCancel = 1,
193 // NSApplicationDelegateReplyFailure = 2
196 - (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
198 return [[NSUserDefaults standardUserDefaults]
199 boolForKey:MMTerminateAfterLastWindowClosedKey];
202 - (NSApplicationTerminateReply)applicationShouldTerminate:
203 (NSApplication *)sender
205 // TODO: Follow Apple's guidelines for 'Graceful Application Termination'
206 // (in particular, allow user to review changes and save).
207 int reply = NSTerminateNow;
208 BOOL modifiedBuffers = NO;
210 // Go through windows, checking for modified buffers. (Each Vim process
211 // tells MacVim when any buffer has been modified and MacVim sets the
212 // 'documentEdited' flag of the window correspondingly.)
213 NSEnumerator *e = [[NSApp windows] objectEnumerator];
215 while (window = [e nextObject]) {
216 if ([window isDocumentEdited]) {
217 modifiedBuffers = YES;
222 if (modifiedBuffers) {
223 NSAlert *alert = [[NSAlert alloc] init];
224 [alert addButtonWithTitle:@"Quit"];
225 [alert addButtonWithTitle:@"Cancel"];
226 [alert setMessageText:@"Quit without saving?"];
227 [alert setInformativeText:@"There are modified buffers, "
228 "if you quit now all changes will be lost. Quit anyway?"];
229 [alert setAlertStyle:NSWarningAlertStyle];
231 if ([alert runModal] != NSAlertFirstButtonReturn)
232 reply = NSTerminateCancel;
240 - (void)applicationWillTerminate:(NSNotification *)aNotification
242 // This will invalidate all connections (since they were spawned from the
243 // default connection).
244 [[NSConnection defaultConnection] invalidate];
246 // Send a SIGINT to all running Vim processes, so that they are sure to
247 // receive the connectionDidDie: notification (a process has to be checking
248 // the run-loop for this to happen).
249 unsigned i, count = [vimControllers count];
250 for (i = 0; i < count; ++i) {
251 MMVimController *controller = [vimControllers objectAtIndex:i];
252 int pid = [controller pid];
257 if (fontContainerRef) {
258 ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
259 fontContainerRef = 0;
262 // TODO: Is this a correct way of releasing the MMAppController?
263 // (It doesn't seem like dealloc is ever called.)
264 [NSApp setDelegate:nil];
268 - (void)removeVimController:(id)controller
270 //NSLog(@"%s%@", _cmd, controller);
272 [[controller windowController] close];
274 [vimControllers removeObject:controller];
276 if (![vimControllers count]) {
277 // Turn on autoenabling of menus (because no Vim is open to handle it),
278 // but do not touch the MacVim menu. Note that the menus must be
279 // enabled first otherwise autoenabling does not work.
280 NSMenu *mainMenu = [NSApp mainMenu];
281 int i, count = [mainMenu numberOfItems];
282 for (i = 1; i < count; ++i) {
283 NSMenuItem *item = [mainMenu itemAtIndex:i];
284 [item setEnabled:YES];
285 [[item submenu] recurseSetAutoenablesItems:YES];
290 - (void)windowControllerWillOpen:(MMWindowController *)windowController
292 NSPoint topLeft = NSZeroPoint;
293 NSWindow *keyWin = [NSApp keyWindow];
294 NSWindow *win = [windowController window];
298 // If there is a key window, cascade from it, otherwise use the autosaved
299 // window position (if any).
301 NSRect frame = [keyWin frame];
302 topLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
304 NSString *topLeftString = [[NSUserDefaults standardUserDefaults]
305 stringForKey:MMTopLeftPointKey];
307 topLeft = NSPointFromString(topLeftString);
310 if (!NSEqualPoints(topLeft, NSZeroPoint)) {
312 topLeft = [win cascadeTopLeftFromPoint:topLeft];
314 [win setFrameTopLeftPoint:topLeft];
317 if (openSelectionString) {
318 // There is some text to paste into this window as a result of the
319 // services menu "Open selection ..." being used.
320 [[windowController vimController] dropString:openSelectionString];
321 [openSelectionString release];
322 openSelectionString = nil;
326 - (IBAction)newWindow:(id)sender
328 [self launchVimProcessWithArguments:nil];
331 - (IBAction)selectNextWindow:(id)sender
333 unsigned i, count = [vimControllers count];
336 NSWindow *keyWindow = [NSApp keyWindow];
337 for (i = 0; i < count; ++i) {
338 MMVimController *vc = [vimControllers objectAtIndex:i];
339 if ([[[vc windowController] window] isEqual:keyWindow])
346 MMVimController *vc = [vimControllers objectAtIndex:i];
347 [[vc windowController] showWindow:self];
351 - (IBAction)selectPreviousWindow:(id)sender
353 unsigned i, count = [vimControllers count];
356 NSWindow *keyWindow = [NSApp keyWindow];
357 for (i = 0; i < count; ++i) {
358 MMVimController *vc = [vimControllers objectAtIndex:i];
359 if ([[[vc windowController] window] isEqual:keyWindow])
369 MMVimController *vc = [vimControllers objectAtIndex:i];
370 [[vc windowController] showWindow:self];
374 - (IBAction)fontSizeUp:(id)sender
376 [[NSFontManager sharedFontManager] modifyFont:
377 [NSNumber numberWithInt:NSSizeUpFontAction]];
380 - (IBAction)fontSizeDown:(id)sender
382 [[NSFontManager sharedFontManager] modifyFont:
383 [NSNumber numberWithInt:NSSizeDownFontAction]];
386 - (byref id <MMFrontendProtocol>)
387 connectBackend:(byref in id <MMBackendProtocol>)backend
390 //NSLog(@"Frontend got connection request from backend...adding new "
391 // "MMVimController");
393 [(NSDistantObject*)backend
394 setProtocolForProxy:@protocol(MMBackendProtocol)];
396 MMVimController *vc = [[[MMVimController alloc]
397 initWithBackend:backend pid:pid] autorelease];
399 if (![vimControllers count]) {
400 // The first window autosaves its position. (The autosaving features
401 // of Cocoa are not used because we need more control over what is
402 // autosaved and when it is restored.)
403 [[vc windowController] setWindowAutosaveKey:MMTopLeftPointKey];
406 [vimControllers addObject:vc];
408 // HACK! MacVim does not get activated if it is launched from the
409 // terminal, so we forcibly activate here unless it is an untitled window
410 // opening (i.e. MacVim was opened from the Finder). Untitled windows are
411 // treated differently, else MacVim would steal the focus if another app
412 // was activated while the untitled window was loading.
413 if (!untitledWindowOpening)
414 [NSApp activateIgnoringOtherApps:YES];
416 untitledWindowOpening = NO;
421 - (NSArray *)serverList
423 NSMutableArray *array = [NSMutableArray array];
425 unsigned i, count = [vimControllers count];
426 for (i = 0; i < count; ++i) {
427 MMVimController *controller = [vimControllers objectAtIndex:i];
428 if ([controller serverName])
429 [array addObject:[controller serverName]];
435 @end // MMAppController
440 @implementation MMAppController (MMServices)
442 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
443 error:(NSString **)error
445 if (![[pboard types] containsObject:NSStringPboardType]) {
446 NSLog(@"WARNING: Pasteboard contains no object of type "
447 "NSStringPboardType");
451 MMVimController *vc = [self topmostVimController];
453 // Open a new tab first, since dropString: does not do this.
454 [vc sendMessage:AddNewTabMsgID data:nil];
455 [vc dropString:[pboard stringForType:NSStringPboardType]];
457 // NOTE: There is no window to paste the selection into, so save the
458 // text, open a new window, and paste the text when the next window
459 // opens. (If this is called several times in a row, then all but the
460 // last call might be ignored.)
461 if (openSelectionString) [openSelectionString release];
462 openSelectionString = [[pboard stringForType:NSStringPboardType] copy];
464 [self newWindow:self];
468 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
469 error:(NSString **)error
471 if (![[pboard types] containsObject:NSStringPboardType]) {
472 NSLog(@"WARNING: Pasteboard contains no object of type "
473 "NSStringPboardType");
477 // TODO: Parse multiple filenames and create array with names.
478 NSString *string = [pboard stringForType:NSStringPboardType];
479 string = [string stringByTrimmingCharactersInSet:
480 [NSCharacterSet whitespaceAndNewlineCharacterSet]];
481 string = [string stringByStandardizingPath];
483 MMVimController *vc = nil;
484 if (userData && [userData isEqual:@"Tab"])
485 vc = [self topmostVimController];
488 [vc dropFiles:[NSArray arrayWithObject:string]];
490 [self application:NSApp openFiles:[NSArray arrayWithObject:string]];
494 @end // MMAppController (MMServices)
499 @implementation MMAppController (Private)
501 - (MMVimController *)keyVimController
503 NSWindow *keyWindow = [NSApp keyWindow];
505 unsigned i, count = [vimControllers count];
506 for (i = 0; i < count; ++i) {
507 MMVimController *vc = [vimControllers objectAtIndex:i];
508 if ([[[vc windowController] window] isEqual:keyWindow])
516 - (MMVimController *)topmostVimController
518 NSArray *windows = [NSApp orderedWindows];
519 if ([windows count] > 0) {
520 NSWindow *window = [windows objectAtIndex:0];
521 unsigned i, count = [vimControllers count];
522 for (i = 0; i < count; ++i) {
523 MMVimController *vc = [vimControllers objectAtIndex:i];
524 if ([[[vc windowController] window] isEqual:window])
532 - (void)launchVimProcessWithArguments:(NSArray *)args
534 NSString *path = [[NSBundle mainBundle] pathForAuxiliaryExecutable:@"Vim"];
536 NSLog(@"ERROR: Vim executable could not be found inside app bundle!");
540 NSMutableString *execArg = [NSMutableString
541 stringWithFormat:@"exec \"%@\" -g", path];
543 // Append all arguments while making sure that arguments containing
544 // spaces are enclosed in quotes.
545 NSCharacterSet *space = [NSCharacterSet whitespaceCharacterSet];
546 unsigned i, count = [args count];
548 for (i = 0; i < count; ++i) {
549 NSString *arg = [args objectAtIndex:i];
550 if (NSNotFound != [arg rangeOfCharacterFromSet:space].location)
551 [execArg appendFormat:@" \"%@\"", arg];
553 [execArg appendFormat:@" %@", arg];
557 // Launch the process with a login shell so that users environment settings
558 // get sourced. This does not always happen when MacVim is started.
559 NSArray *shellArgs = [NSArray arrayWithObjects:@"-l", @"-c", execArg, nil];
560 NSString *shell = [[[NSProcessInfo processInfo] environment]
561 objectForKey:@"SHELL"];
565 //NSLog(@"Launching: %@ args: %@", shell, shellArgs);
566 [NSTask launchedTaskWithLaunchPath:shell arguments:shellArgs];
569 @end // MMAppController (Private)
574 @implementation NSMenu (MMExtras)
576 - (void)recurseSetAutoenablesItems:(BOOL)on
578 [self setAutoenablesItems:on];
580 int i, count = [self numberOfItems];
581 for (i = 0; i < count; ++i) {
582 NSMenuItem *item = [self itemAtIndex:i];
583 [item setEnabled:YES];
584 NSMenu *submenu = [item submenu];
586 [submenu recurseSetAutoenablesItems:on];
591 @end // NSMenu (MMExtras)
596 @implementation NSNumber (MMExtras)
599 return [self intValue];
601 @end // NSNumber (MMExtras)