Changed DiffText and Constant highlight groups
[MacVim/jjgod.git] / src / MacVim / MMAppController.m
blobbfbc8c7da1cf22999d8ba4a3cb1650800476e587
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
43 @interface NSNumber (MMExtras)
44 - (int)tag;
45 @end
49 @implementation MMAppController
51 + (void)initialize
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,
68         nil];
70     [[NSUserDefaults standardUserDefaults] registerDefaults:dict];
72     NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
73     [NSApp registerServicesMenuSendTypes:types returnTypes:types];
76 - (id)init
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];
98         } else {
99             NSLog(@"WARNING: Failed to register connection with name '%@'",
100                     name);
101         }
102     }
104     return self;
107 - (void)dealloc
109     //NSLog(@"MMAppController dealloc");
111     [vimControllers release];
112     [openSelectionString release];
114     [super dealloc];
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];
136     return YES;
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;
150         }
151     }
153     if (firstMissingFile) {
154         NSAlert *alert = [[NSAlert alloc] init];
155         [alert addButtonWithTitle:@"OK"];
157         NSString *text;
158         if ([files count] >= count-1) {
159             [alert setMessageText:@"File not found"];
160             text = [NSString stringWithFormat:@"Could not open file with "
161                 "name %@.", firstMissingFile];
162         } else {
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];
167         }
169         [alert setInformativeText:text];
170         [alert setAlertStyle:NSWarningAlertStyle];
172         [alert runModal];
173         [alert release];
175         [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
176         return;
177     }
179     MMVimController *vc;
180     BOOL openInTabs = [[NSUserDefaults standardUserDefaults]
181         boolForKey:MMOpenFilesInTabsKey];
183     if (openInTabs && (vc = [self topmostVimController])) {
184         [vc dropFiles:files];
185     } else {
186         NSMutableArray *args = [NSMutableArray arrayWithObjects:
187             @"-g", @"-p", nil];
188         [args addObjectsFromArray:files];
190         NSString *path = [[NSBundle mainBundle]
191                 pathForAuxiliaryExecutable:@"Vim"];
192         if (!path) {
193             NSLog(@"ERROR: Vim executable could not be found inside app "
194                    "bundle!");
195             [NSApp replyToOpenOrPrint:NSApplicationDelegateReplyFailure];
196             return;
197         }
199         [NSTask launchedTaskWithLaunchPath:path arguments:args];
200     }
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];
228         if (connection) {
229             NSTimeInterval req = [connection requestTimeout];
230             NSTimeInterval rep = [connection replyTimeout];
231             [connection setRequestTimeout:MMTerminateTimeout];
232             [connection setReplyTimeout:MMTerminateTimeout];
234             @try {
235                 if ([proxy checkForModifiedBuffers])
236                     modifiedBuffers = YES;
237             }
238             @catch (NSException *e) {
239                 NSLog(@"WARNING: Got exception while waiting for "
240                         "checkForModifiedBuffers: \"%@\"", e);
241                 notResponding = YES;
242             }
243             @finally {
244                 [connection setRequestTimeout:req];
245                 [connection setReplyTimeout:rep];
246                 if (modifiedBuffers || notResponding)
247                     break;
248             }
249         }
250     }
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?"];
260         } else {
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?"];
265         }
266         [alert setAlertStyle:NSWarningAlertStyle];
268         if ([alert runModal] != NSAlertFirstButtonReturn) {
269             reply = NSTerminateCancel;
270         }
272         [alert release];
273     }
275     return reply;
278 - (void)applicationWillTerminate:(NSNotification *)aNotification
280     // Send a SIGINT to all running Vim processes, so that they are sure to
281     // receive the connectionDidDie: notification (a process has to checking
282     // the run-loop for this to happen).
283     unsigned i, count = [vimControllers count];
284     for (i = 0; i < count; ++i) {
285         MMVimController *controller = [vimControllers objectAtIndex:i];
286         int pid = [controller pid];
287         if (pid > 0)
288             kill(pid, SIGINT);
290         id proxy = [controller backendProxy];
291         NSConnection *connection = [proxy connectionForProxy];
292         if (connection) {
293             [connection invalidate];
294         }
295     }
297     if (fontContainerRef) {
298         ATSFontDeactivate(fontContainerRef, NULL, kATSOptionFlagsDefault);
299         fontContainerRef = 0;
300     }
302     // TODO: Is this a correct way of releasing the MMAppController?
303     // (It doesn't seem like dealloc is ever called.)
304     [NSApp setDelegate:nil];
305     [self autorelease];
308 - (void)removeVimController:(id)controller
310     //NSLog(@"%s%@", _cmd, controller);
312     [[controller windowController] close];
314     [vimControllers removeObject:controller];
316     if (![vimControllers count]) {
317         // Turn on autoenabling of menus (because no Vim is open to handle it),
318         // but do not touch the MacVim menu.  Note that the menus must be
319         // enabled first otherwise autoenabling does not work.
320         NSMenu *mainMenu = [NSApp mainMenu];
321         int i, count = [mainMenu numberOfItems];
322         for (i = 1; i < count; ++i) {
323             NSMenuItem *item = [mainMenu itemAtIndex:i];
324             [item setEnabled:YES];
325             [[item submenu] recurseSetAutoenablesItems:YES];
326         }
327     }
330 - (void)windowControllerWillOpen:(MMWindowController *)windowController
332     NSPoint topLeft = NSZeroPoint;
333     NSWindow *keyWin = [NSApp keyWindow];
334     NSWindow *win = [windowController window];
336     if (!win) return;
338     // If there is a key window, cascade from it, otherwise use the autosaved
339     // window position (if any).
340     if (keyWin) {
341         NSRect frame = [keyWin frame];
342         topLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
343     } else {
344         NSString *topLeftString = [[NSUserDefaults standardUserDefaults]
345             stringForKey:MMTopLeftPointKey];
346         if (topLeftString)
347             topLeft = NSPointFromString(topLeftString);
348     }
350     if (!NSEqualPoints(topLeft, NSZeroPoint)) {
351         if (keyWin)
352             topLeft = [win cascadeTopLeftFromPoint:topLeft];
354         [win setFrameTopLeftPoint:topLeft];
355     }
357     if (openSelectionString) {
358         // There is some text to paste into this window as a result of the
359         // services menu "Open selection ..." being used.
360         [[windowController vimController] dropString:openSelectionString];
361         [openSelectionString release];
362         openSelectionString = nil;
363     }
366 - (IBAction)newWindow:(id)sender
368     NSMutableArray *args = [NSMutableArray arrayWithObject:@"-g"];
369     NSString *path = [[NSBundle mainBundle]
370             pathForAuxiliaryExecutable:@"Vim"];
371     if (!path) {
372         NSLog(@"ERROR: Vim executable could not be found inside app bundle!");
373         return;
374     }
377     //NSLog(@"Launching a new VimTask...");
378     [NSTask launchedTaskWithLaunchPath:path arguments:args];
381 - (IBAction)selectNextWindow:(id)sender
383     unsigned i, count = [vimControllers count];
384     if (!count) return;
386     NSWindow *keyWindow = [NSApp keyWindow];
387     for (i = 0; i < count; ++i) {
388         MMVimController *vc = [vimControllers objectAtIndex:i];
389         if ([[[vc windowController] window] isEqual:keyWindow])
390             break;
391     }
393     if (i < count) {
394         if (++i >= count)
395             i = 0;
396         MMVimController *vc = [vimControllers objectAtIndex:i];
397         [[vc windowController] showWindow:self];
398     }
401 - (IBAction)selectPreviousWindow:(id)sender
403     unsigned i, count = [vimControllers count];
404     if (!count) return;
406     NSWindow *keyWindow = [NSApp keyWindow];
407     for (i = 0; i < count; ++i) {
408         MMVimController *vc = [vimControllers objectAtIndex:i];
409         if ([[[vc windowController] window] isEqual:keyWindow])
410             break;
411     }
413     if (i < count) {
414         if (i > 0) {
415             --i;
416         } else {
417             i = count - 1;
418         }
419         MMVimController *vc = [vimControllers objectAtIndex:i];
420         [[vc windowController] showWindow:self];
421     }
424 - (IBAction)fontSizeUp:(id)sender
426     [[NSFontManager sharedFontManager] modifyFont:
427             [NSNumber numberWithInt:NSSizeUpFontAction]];
430 - (IBAction)fontSizeDown:(id)sender
432     [[NSFontManager sharedFontManager] modifyFont:
433             [NSNumber numberWithInt:NSSizeDownFontAction]];
436 - (byref id <MMFrontendProtocol>)
437     connectBackend:(byref in id <MMBackendProtocol>)backend
438                pid:(int)pid
440     //NSLog(@"Frontend got connection request from backend...adding new "
441     //        "MMVimController");
443     [(NSDistantObject*)backend
444             setProtocolForProxy:@protocol(MMBackendProtocol)];
446     MMVimController *vc = [[[MMVimController alloc]
447             initWithBackend:backend pid:pid] autorelease];
449     if (![vimControllers count]) {
450         // The first window autosaves its position.  (The autosaving features
451         // of Cocoa are not used because we need more control over what is
452         // autosaved and when it is restored.)
453         [[vc windowController] setWindowAutosaveKey:MMTopLeftPointKey];
454     }
456     [vimControllers addObject:vc];
458     // HACK!  MacVim does not get activated if it is launched from the
459     // terminal, so we forcibly activate here unless it is an untitled window
460     // opening (i.e. MacVim was opened from the Finder).  Untitled windows are
461     // treated differently, else MacVim would steal the focus if another app
462     // was activated while the untitled window was loading.
463     if (!untitledWindowOpening)
464         [NSApp activateIgnoringOtherApps:YES];
466     untitledWindowOpening = NO;
468     return vc;
471 - (NSArray *)serverList
473     NSMutableArray *array = [NSMutableArray array];
475     unsigned i, count = [vimControllers count];
476     for (i = 0; i < count; ++i) {
477         MMVimController *controller = [vimControllers objectAtIndex:i];
478         if ([controller serverName])
479             [array addObject:[controller serverName]];
480     }
482     return array;
485 @end // MMAppController
490 @implementation MMAppController (MMServices)
492 - (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
493                 error:(NSString **)error
495     if (![[pboard types] containsObject:NSStringPboardType]) {
496         NSLog(@"WARNING: Pasteboard contains no object of type "
497                 "NSStringPboardType");
498         return;
499     }
501     MMVimController *vc = [self topmostVimController];
502     if (vc) {
503         // Open a new tab first, since dropString: does not do this.
504         [vc sendMessage:AddNewTabMsgID data:nil];
505         [vc dropString:[pboard stringForType:NSStringPboardType]];
506     } else {
507         // NOTE: There is no window to paste the selection into, so save the
508         // text, open a new window, and paste the text when the next window
509         // opens.  (If this is called several times in a row, then all but the
510         // last call might be ignored.)
511         if (openSelectionString) [openSelectionString release];
512         openSelectionString = [[pboard stringForType:NSStringPboardType] copy];
514         [self newWindow:self];
515     }
518 - (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
519            error:(NSString **)error
521     if (![[pboard types] containsObject:NSStringPboardType]) {
522         NSLog(@"WARNING: Pasteboard contains no object of type "
523                 "NSStringPboardType");
524         return;
525     }
527     // TODO: Parse multiple filenames and create array with names.
528     NSString *string = [pboard stringForType:NSStringPboardType];
529     string = [string stringByTrimmingCharactersInSet:
530             [NSCharacterSet whitespaceAndNewlineCharacterSet]];
531     string = [string stringByStandardizingPath];
533     MMVimController *vc = nil;
534     if (userData && [userData isEqual:@"Tab"])
535         vc = [self topmostVimController];
537     if (vc) {
538         [vc dropFiles:[NSArray arrayWithObject:string]];
539     } else {
540         [self application:NSApp openFiles:[NSArray arrayWithObject:string]];
541     }
544 @end // MMAppController (MMServices)
549 @implementation MMAppController (Private)
551 - (MMVimController *)keyVimController
553     NSWindow *keyWindow = [NSApp keyWindow];
554     if (keyWindow) {
555         unsigned i, count = [vimControllers count];
556         for (i = 0; i < count; ++i) {
557             MMVimController *vc = [vimControllers objectAtIndex:i];
558             if ([[[vc windowController] window] isEqual:keyWindow])
559                 return vc;
560         }
561     }
563     return nil;
566 - (MMVimController *)topmostVimController
568     NSArray *windows = [NSApp orderedWindows];
569     if ([windows count] > 0) {
570         NSWindow *window = [windows objectAtIndex:0];
571         unsigned i, count = [vimControllers count];
572         for (i = 0; i < count; ++i) {
573             MMVimController *vc = [vimControllers objectAtIndex:i];
574             if ([[[vc windowController] window] isEqual:window])
575                 return vc;
576         }
577     }
579     return nil;
582 @end
587 @implementation NSMenu (MMExtras)
589 - (void)recurseSetAutoenablesItems:(BOOL)on
591     [self setAutoenablesItems:on];
593     int i, count = [self numberOfItems];
594     for (i = 0; i < count; ++i) {
595         NSMenuItem *item = [self itemAtIndex:i];
596         [item setEnabled:YES];
597         NSMenu *submenu = [item submenu];
598         if (submenu) {
599             [submenu recurseSetAutoenablesItems:on];
600         }
601     }
604 @end  // NSMenu (MMExtras)
609 @implementation NSNumber (MMExtras)
610 - (int)tag
612     return [self intValue];
614 @end // NSNumber (MMExtras)