Ctrl-PageUp/PageDown now get passed on to Vim
[MacVim/jjgod.git] / MMAppController.m
blob496cf53f0fe583d17fafaf582eed7bdc5c9a6c3a
1 /* vi:set ts=8 sts=4 sw=4 ft=objc:
2  *
3  * VIM - Vi IMproved            by Bram Moolenaar
4  *                              MacVim GUI port by Bjorn Winckler
5  *
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.
9  */
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;
31 @end
34 @interface MMAppController (Private)
35 - (MMVimController *)keyVimController;
36 - (MMVimController *)topmostVimController;
37 @end
39 @interface NSMenu (MMExtras)
40 - (void)recurseSetAutoenablesItems:(BOOL)on;
41 @end
45 @implementation MMAppController
47 + (void)initialize
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,
64         nil];
66     [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
68     NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
69     [NSApp registerServicesMenuSendTypes:types returnTypes:types];
72 - (id)init
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];
94         } else {
95             NSLog(@"WARNING: Failed to register connection with name '%@'",
96                     name);
97         }
98     }
100     return self;
103 - (void)dealloc
105     //NSLog(@"MMAppController dealloc");
107     [vimControllers release];
108     [openSelectionString release];
110     [super dealloc];
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];
132     return YES;
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;
146         }
147     }
149     if (firstMissingFile) {
150         NSAlert *alert = [[NSAlert alloc] init];
151         [alert addButtonWithTitle:@"OK"];
153         NSString *text;
154         if ([files count] >= count-1) {
155             [alert setMessageText:@"File not found"];
156             text = [NSString stringWithFormat:@"Could not open file with "
157                 "name %@.", firstMissingFile];
158         } else {
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];
163         }
165         [alert setInformativeText:text];
166         [alert setAlertStyle:NSWarningAlertStyle];
168         [alert runModal];
169         [alert release];
171         [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
172         return;
173     }
175     MMVimController *vc;
176     BOOL openInTabs = [[NSUserDefaults standardUserDefaults]
177         boolForKey:MMOpenFilesInTabsKey];
179     if (openInTabs && (vc = [self topmostVimController])) {
180         [vc dropFiles:files];
181     } else {
182         NSMutableArray *args = [NSMutableArray arrayWithObjects:
183             @"-g", @"-p", nil];
184         [args addObjectsFromArray:files];
186         NSString *path = [[NSBundle mainBundle]
187                 pathForAuxiliaryExecutable:@"Vim"];
188         if (!path) {
189             NSLog(@"ERROR: Vim executable could not be found inside app "
190                    "bundle!");
191             [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
192             return;
193         }
195         [NSTask launchedTaskWithLaunchPath:path arguments:args];
196     }
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];
224         if (connection) {
225             NSTimeInterval req = [connection requestTimeout];
226             NSTimeInterval rep = [connection replyTimeout];
227             [connection setRequestTimeout:MMTerminateTimeout];
228             [connection setReplyTimeout:MMTerminateTimeout];
230             @try {
231                 if ([proxy checkForModifiedBuffers])
232                     modifiedBuffers = YES;
233             }
234             @catch (NSException *e) {
235                 NSLog(@"WARNING: Got exception while waiting for "
236                         "checkForModifiedBuffers: \"%@\"", e);
237                 notResponding = YES;
238             }
239             @finally {
240                 [connection setRequestTimeout:req];
241                 [connection setReplyTimeout:rep];
242                 if (modifiedBuffers || notResponding)
243                     break;
244             }
245         }
246     }
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?"];
256         } else {
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?"];
261         }
262         [alert setAlertStyle:NSWarningAlertStyle];
264         if ([alert runModal] != NSAlertFirstButtonReturn) {
265             reply = NSTerminateCancel;
266         }
268         [alert release];
269     }
271     return reply;
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];
283         if (pid > 0)
284             kill(pid, SIGINT);
286         id proxy = [controller backendProxy];
287         NSConnection *connection = [proxy connectionForProxy];
288         if (connection) {
289             [connection invalidate];
290         }
291     }
293     if (fontContainerRef) {
294         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
295         fontContainerRef = 0;
296     }
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];
301     [self autorelease];
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];
320         }
321     }
324 - (void)windowControllerWillOpen:(MMWindowController *)windowController
326     NSPoint topLeft = NSZeroPoint;
327     NSWindow *keyWin = [NSApp keyWindow];
328     NSWindow *win = [windowController window];
330     if (!win) return;
332     // If there is a key window, cascade from it, otherwise use the autosaved
333     // window position (if any).
334     if (keyWin) {
335         NSRect frame = [keyWin frame];
336         topLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
337     } else {
338         NSString *topLeftString = [[NSUserDefaults standardUserDefaults]
339             stringForKey:MMTopLeftPointKey];
340         if (topLeftString)
341             topLeft = NSPointFromString(topLeftString);
342     }
344     if (!NSEqualPoints(topLeft, NSZeroPoint)) {
345         if (keyWin)
346             topLeft = [win cascadeTopLeftFromPoint:topLeft];
348         [win setFrameTopLeftPoint:topLeft];
349     }
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;
357     }
360 - (IBAction)newWindow:(id)sender
362     NSMutableArray *args = [NSMutableArray arrayWithObject:@"-g"];
363     NSString *path = [[NSBundle mainBundle]
364             pathForAuxiliaryExecutable:@"Vim"];
365     if (!path) {
366         NSLog(@"ERROR: Vim executable could not be found inside app bundle!");
367         return;
368     }
371     //NSLog(@"Launching a new VimTask...");
372     [NSTask launchedTaskWithLaunchPath:path arguments:args];
375 - (IBAction)selectNextWindow:(id)sender
377     unsigned i, count = [vimControllers count];
378     if (!count) return;
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])
384             break;
385     }
387     if (i < count) {
388         if (++i >= count)
389             i = 0;
390         MMVimController *vc = [vimControllers objectAtIndex:i];
391         [[vc windowController] showWindow:self];
392     }
395 - (IBAction)selectPreviousWindow:(id)sender
397     unsigned i, count = [vimControllers count];
398     if (!count) return;
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])
404             break;
405     }
407     if (i < count) {
408         if (i > 0) {
409             --i;
410         } else {
411             i = count - 1;
412         }
413         MMVimController *vc = [vimControllers objectAtIndex:i];
414         [[vc windowController] showWindow:self];
415     }
418 - (byref id <MMFrontendProtocol>)
419     connectBackend:(byref in id <MMBackendProtocol>)backend
420                pid:(int)pid
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];
436     }
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;
450     return vc;
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]];
462     }
464     return array;
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");
480         return;
481     }
483     MMVimController *vc = [self topmostVimController];
484     if (vc) {
485         // Open a new tab first, since dropString: does not do this.
486         [vc sendMessage:AddNewTabMsgID data:nil];
487         [vc dropString:[pboard stringForType:NSStringPboardType]];
488     } else {
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];
497     }
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");
506         return;
507     }
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];
519     if (vc) {
520         [vc dropFiles:[NSArray arrayWithObject:string]];
521     } else {
522         [self application:NSApp openFiles:[NSArray arrayWithObject:string]];
523     }
526 @end // MMAppController (MMServices)
531 @implementation MMAppController (Private)
533 - (MMVimController *)keyVimController
535     NSWindow *keyWindow = [NSApp keyWindow];
536     if (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])
541                 return vc;
542         }
543     }
545     return nil;
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])
557                 return vc;
558         }
559     }
561     return nil;
564 @end
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];
578         if (submenu) {
579             [submenu recurseSetAutoenablesItems:on];
580         }
581     }
584 @end  // NSMenu (MMExtras)