mirror of
https://github.com/macvim-dev/macvim.git
synced 2026-06-07 15:37:14 +02:00
4588872456
git-svn-id: http://macvim.googlecode.com/svn/trunk@118 96c4425d-ca35-0410-94e5-3396d5c13a8f
503 lines
15 KiB
Objective-C
503 lines
15 KiB
Objective-C
/* vi:set ts=8 sts=4 sw=4 ft=objc:
|
|
*
|
|
* VIM - Vi IMproved by Bram Moolenaar
|
|
* MacVim GUI port by Bjorn Winckler
|
|
*
|
|
* Do ":help uganda" in Vim to read copying and usage conditions.
|
|
* Do ":help credits" in Vim to see a list of people who contributed.
|
|
* See README.txt for an overview of the Vim source code.
|
|
*/
|
|
|
|
#import "MMAppController.h"
|
|
#import "MMVimController.h"
|
|
#import "MMWindowController.h"
|
|
|
|
|
|
|
|
// NSUserDefaults keys
|
|
NSString *MMNoWindowKey = @"nowindow";
|
|
NSString *MMTabMinWidthKey = @"tabminwidth";
|
|
NSString *MMTabMaxWidthKey = @"tabmaxwidth";
|
|
NSString *MMTabOptimumWidthKey = @"taboptimumwidth";
|
|
NSString *MMStatuslineOffKey = @"statuslineoff";
|
|
NSString *MMTextInsetLeftKey = @"insetleft";
|
|
NSString *MMTextInsetRightKey = @"insetright";
|
|
NSString *MMTextInsetTopKey = @"insettop";
|
|
NSString *MMTextInsetBottomKey = @"insetbottom";
|
|
NSString *MMTerminateAfterLastWindowClosedKey
|
|
= @"terminateafterlastwindowclosed";
|
|
NSString *MMTypesetterKey = @"typesetter";
|
|
NSString *MMCellWidthMultiplierKey = @"cellwidthmultiplier";
|
|
NSString *MMBaselineOffsetKey = @"baselineoffset";
|
|
NSString *MMTranslateCtrlClickKey = @"translatectrlclick";
|
|
|
|
|
|
// Key in user defaults for autosave data.
|
|
NSString *MMAutosaveKey = @"DefaultWindow";
|
|
|
|
|
|
|
|
@interface MMAppController (MMServices)
|
|
- (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
|
|
error:(NSString **)error;
|
|
- (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
|
|
error:(NSString **)error;
|
|
@end
|
|
|
|
|
|
@interface MMAppController (Private)
|
|
- (MMVimController *)keyVimController;
|
|
- (MMVimController *)topmostVimController;
|
|
@end
|
|
|
|
@interface NSMenu (MMExtras)
|
|
- (void)recurseSetAutoenablesItems:(BOOL)on;
|
|
@end
|
|
|
|
|
|
|
|
@implementation MMAppController
|
|
|
|
+ (void)initialize
|
|
{
|
|
NSDictionary *dict = [NSDictionary dictionaryWithObjectsAndKeys:
|
|
[NSNumber numberWithBool:NO], MMNoWindowKey,
|
|
[NSNumber numberWithInt:64], MMTabMinWidthKey,
|
|
[NSNumber numberWithInt:6*64], MMTabMaxWidthKey,
|
|
[NSNumber numberWithInt:132], MMTabOptimumWidthKey,
|
|
[NSNumber numberWithBool:NO], MMStatuslineOffKey,
|
|
[NSNumber numberWithInt:2], MMTextInsetLeftKey,
|
|
[NSNumber numberWithInt:1], MMTextInsetRightKey,
|
|
[NSNumber numberWithInt:1], MMTextInsetTopKey,
|
|
[NSNumber numberWithInt:1], MMTextInsetBottomKey,
|
|
[NSNumber numberWithBool:NO], MMTerminateAfterLastWindowClosedKey,
|
|
@"MMTypesetter", MMTypesetterKey,
|
|
[NSNumber numberWithFloat:1], MMCellWidthMultiplierKey,
|
|
[NSNumber numberWithFloat:-1], MMBaselineOffsetKey,
|
|
[NSNumber numberWithBool:YES], MMTranslateCtrlClickKey,
|
|
nil];
|
|
|
|
[[NSUserDefaults standardUserDefaults] registerDefaults:dict];
|
|
|
|
NSArray *types = [NSArray arrayWithObject:NSStringPboardType];
|
|
[NSApp registerServicesMenuSendTypes:types returnTypes:types];
|
|
}
|
|
|
|
- (id)init
|
|
{
|
|
if ((self = [super init])) {
|
|
vimControllers = [NSMutableArray new];
|
|
|
|
// NOTE! If the name of the connection changes here it must also be
|
|
// updated in MMBackend.m.
|
|
NSConnection *connection = [NSConnection defaultConnection];
|
|
NSString *name = [NSString stringWithFormat:@"%@-connection",
|
|
[[NSBundle mainBundle] bundleIdentifier]];
|
|
//NSLog(@"Registering connection with name '%@'", name);
|
|
if ([connection registerName:name]) {
|
|
[connection setRootObject:self];
|
|
|
|
// NOTE: When the user is resizing the window the AppKit puts the
|
|
// run loop in event tracking mode. Unless the connection listens
|
|
// to request in this mode, live resizing won't work.
|
|
[connection addRequestMode:NSEventTrackingRunLoopMode];
|
|
} else {
|
|
NSLog(@"WARNING: Failed to register connection with name '%@'",
|
|
name);
|
|
}
|
|
}
|
|
|
|
return self;
|
|
}
|
|
|
|
- (void)dealloc
|
|
{
|
|
//NSLog(@"MMAppController dealloc");
|
|
|
|
[vimControllers release];
|
|
|
|
[super dealloc];
|
|
}
|
|
|
|
- (void)applicationDidFinishLaunching:(NSNotification *)notification
|
|
{
|
|
[NSApp setServicesProvider:self];
|
|
}
|
|
|
|
- (BOOL)applicationShouldOpenUntitledFile:(NSApplication *)sender
|
|
{
|
|
// NOTE! This way it possible to start the app with the command-line
|
|
// argument '-nowindow yes' and no window will be opened by default.
|
|
return ![[NSUserDefaults standardUserDefaults] boolForKey:MMNoWindowKey];
|
|
}
|
|
|
|
- (BOOL)applicationOpenUntitledFile:(NSApplication *)sender
|
|
{
|
|
//NSLog(@"%s NSapp=%@ theApp=%@", _cmd, NSApp, sender);
|
|
|
|
[self newVimWindow:self];
|
|
return YES;
|
|
}
|
|
|
|
- (void)application:(NSApplication *)sender openFiles:(NSArray *)filenames
|
|
{
|
|
NSString *firstMissingFile = nil;
|
|
NSMutableArray *files = [NSMutableArray array];
|
|
int i, count = [filenames count];
|
|
for (i = 0; i < count; ++i) {
|
|
NSString *name = [filenames objectAtIndex:i];
|
|
if ([NSFileHandle fileHandleForReadingAtPath:name]) {
|
|
[files addObject:name];
|
|
} else if (!firstMissingFile) {
|
|
firstMissingFile = name;
|
|
}
|
|
}
|
|
|
|
if (firstMissingFile) {
|
|
NSAlert *alert = [[NSAlert alloc] init];
|
|
[alert addButtonWithTitle:@"OK"];
|
|
|
|
NSString *text;
|
|
if ([files count] >= count-1) {
|
|
[alert setMessageText:@"File not found"];
|
|
text = [NSString stringWithFormat:@"Could not open file with "
|
|
"name %@.", firstMissingFile];
|
|
} else {
|
|
[alert setMessageText:@"Multiple files not found"];
|
|
text = [NSString stringWithFormat:@"Could not open file with "
|
|
"name %@, and %d other files.", firstMissingFile,
|
|
count-[files count]-1];
|
|
}
|
|
|
|
[alert setInformativeText:text];
|
|
[alert setAlertStyle:NSWarningAlertStyle];
|
|
|
|
[alert runModal];
|
|
[alert release];
|
|
|
|
return;
|
|
}
|
|
|
|
NSMutableArray *args = [NSMutableArray arrayWithObjects:@"-g", @"-p", nil];
|
|
[args addObjectsFromArray:files];
|
|
|
|
NSString *path = [[NSBundle mainBundle]
|
|
pathForAuxiliaryExecutable:@"Vim"];
|
|
|
|
[NSTask launchedTaskWithLaunchPath:path arguments:args];
|
|
|
|
[NSApp replyToOpenOrPrint:NSApplicationDelegateReplySuccess];
|
|
// NSApplicationDelegateReplySuccess = 0,
|
|
// NSApplicationDelegateReplyCancel = 1,
|
|
// NSApplicationDelegateReplyFailure = 2
|
|
}
|
|
|
|
- (BOOL)applicationShouldTerminateAfterLastWindowClosed:(NSApplication *)sender
|
|
{
|
|
return [[NSUserDefaults standardUserDefaults]
|
|
boolForKey:MMTerminateAfterLastWindowClosedKey];
|
|
}
|
|
|
|
- (NSApplicationTerminateReply)applicationShouldTerminate:
|
|
(NSApplication *)sender
|
|
{
|
|
int reply = NSTerminateNow;
|
|
BOOL modifiedBuffers = NO;
|
|
|
|
unsigned i, count = [vimControllers count];
|
|
for (i = 0; i < count; ++i) {
|
|
MMVimController *controller = [vimControllers objectAtIndex:i];
|
|
id proxy = [controller backendProxy];
|
|
if (proxy && [proxy checkForModifiedBuffers]) {
|
|
modifiedBuffers = YES;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (modifiedBuffers) {
|
|
NSAlert *alert = [[NSAlert alloc] init];
|
|
[alert addButtonWithTitle:@"Quit"];
|
|
[alert addButtonWithTitle:@"Cancel"];
|
|
[alert setMessageText:@"Quit without saving?"];
|
|
[alert setInformativeText:@"There are modified buffers, "
|
|
" if you quit now all changes will be lost. Quit anyway?"];
|
|
[alert setAlertStyle:NSWarningAlertStyle];
|
|
|
|
if ([alert runModal] != NSAlertFirstButtonReturn) {
|
|
reply = NSTerminateCancel;
|
|
}
|
|
|
|
[alert release];
|
|
}
|
|
|
|
return reply;
|
|
}
|
|
|
|
- (void)applicationWillTerminate:(NSNotification *)aNotification
|
|
{
|
|
// NOTE! Is this a correct way of releasing the MMAppController?
|
|
[NSApp setDelegate:nil];
|
|
[self autorelease];
|
|
}
|
|
|
|
- (void)removeVimController:(id)controller
|
|
{
|
|
[vimControllers removeObject:controller];
|
|
|
|
if (![vimControllers count]) {
|
|
// Turn on autoenabling of menus (because no Vim is open to handle it),
|
|
// but do not touch the MacVim menu.
|
|
NSMenu *mainMenu = [NSApp mainMenu];
|
|
int i, count = [mainMenu numberOfItems];
|
|
for (i = 1; i < count; ++i) {
|
|
NSMenu *submenu = [[mainMenu itemAtIndex:i] submenu];
|
|
[submenu recurseSetAutoenablesItems:YES];
|
|
}
|
|
}
|
|
}
|
|
|
|
- (void)windowControllerWillOpen:(MMWindowController *)windowController
|
|
{
|
|
NSPoint topLeft = NSZeroPoint;
|
|
NSWindow *keyWin = [NSApp keyWindow];
|
|
NSWindow *win = [windowController window];
|
|
|
|
if (!win) return;
|
|
|
|
// If there is a key window, cascade from it, otherwise use the autosaved
|
|
// window position (if any).
|
|
if (keyWin) {
|
|
NSRect frame = [keyWin frame];
|
|
topLeft = NSMakePoint(frame.origin.x, NSMaxY(frame));
|
|
} else {
|
|
NSDictionary *dict = [[NSUserDefaults standardUserDefaults]
|
|
dictionaryForKey:MMAutosaveKey];
|
|
if (dict) {
|
|
id x = [dict objectForKey:@"x"];
|
|
id y = [dict objectForKey:@"y"];
|
|
|
|
if (x && [x isKindOfClass:[NSNumber class]] &&
|
|
y && [y isKindOfClass:[NSNumber class]]) {
|
|
topLeft.x = [x floatValue];
|
|
topLeft.y = [y floatValue];
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!NSEqualPoints(topLeft, NSZeroPoint)) {
|
|
if (keyWin)
|
|
topLeft = [win cascadeTopLeftFromPoint:topLeft];
|
|
|
|
[win setFrameTopLeftPoint:topLeft];
|
|
}
|
|
}
|
|
|
|
- (IBAction)newVimWindow:(id)sender
|
|
{
|
|
NSMutableArray *args = [NSMutableArray arrayWithObject:@"-g"];
|
|
NSString *path = [[NSBundle mainBundle]
|
|
pathForAuxiliaryExecutable:@"Vim"];
|
|
|
|
//NSLog(@"Launching a new VimTask...");
|
|
[NSTask launchedTaskWithLaunchPath:path arguments:args];
|
|
}
|
|
|
|
- (IBAction)selectNextWindow:(id)sender
|
|
{
|
|
unsigned i, count = [vimControllers count];
|
|
if (!count) return;
|
|
|
|
NSWindow *keyWindow = [NSApp keyWindow];
|
|
for (i = 0; i < count; ++i) {
|
|
MMVimController *vc = [vimControllers objectAtIndex:i];
|
|
if ([[[vc windowController] window] isEqual:keyWindow])
|
|
break;
|
|
}
|
|
|
|
if (i < count) {
|
|
if (++i >= count)
|
|
i = 0;
|
|
MMVimController *vc = [vimControllers objectAtIndex:i];
|
|
[[vc windowController] showWindow:self];
|
|
}
|
|
}
|
|
|
|
- (IBAction)selectPreviousWindow:(id)sender
|
|
{
|
|
unsigned i, count = [vimControllers count];
|
|
if (!count) return;
|
|
|
|
NSWindow *keyWindow = [NSApp keyWindow];
|
|
for (i = 0; i < count; ++i) {
|
|
MMVimController *vc = [vimControllers objectAtIndex:i];
|
|
if ([[[vc windowController] window] isEqual:keyWindow])
|
|
break;
|
|
}
|
|
|
|
if (i < count) {
|
|
if (i > 0) {
|
|
--i;
|
|
} else {
|
|
i = count - 1;
|
|
}
|
|
MMVimController *vc = [vimControllers objectAtIndex:i];
|
|
[[vc windowController] showWindow:self];
|
|
}
|
|
}
|
|
|
|
- (byref id <MMFrontendProtocol>)connectBackend:
|
|
(byref in id <MMBackendProtocol>)backend;
|
|
{
|
|
//NSLog(@"Frontend got connection request from backend...adding new "
|
|
// "MMVimController");
|
|
|
|
[(NSDistantObject*)backend
|
|
setProtocolForProxy:@protocol(MMBackendProtocol)];
|
|
|
|
MMVimController *vc = [[[MMVimController alloc] initWithBackend:backend]
|
|
autorelease];
|
|
|
|
if (![vimControllers count]) {
|
|
// The first window autosaves its position. (The autosaving features
|
|
// of Cocoa are not used because we need more control over what is
|
|
// autosaved and when it is restored.)
|
|
[[vc windowController] setWindowAutosaveKey:MMAutosaveKey];
|
|
}
|
|
|
|
[vimControllers addObject:vc];
|
|
|
|
// HACK! MacVim does not get activated if it is launched from the
|
|
// terminal, so we forcibly activate here.
|
|
[NSApp activateIgnoringOtherApps:YES];
|
|
|
|
return vc;
|
|
}
|
|
|
|
@end // MMAppController
|
|
|
|
|
|
|
|
|
|
@implementation MMAppController (MMServices)
|
|
|
|
- (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
|
|
error:(NSString **)error
|
|
{
|
|
if (![[pboard types] containsObject:NSStringPboardType]) {
|
|
NSLog(@"WARNING: Pasteboard contains no object of type "
|
|
"NSStringPboardType");
|
|
return;
|
|
}
|
|
|
|
MMVimController *vc = [self topmostVimController];
|
|
if (vc) {
|
|
NSString *string = [pboard stringForType:NSStringPboardType];
|
|
NSMutableData *data = [NSMutableData data];
|
|
int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding] + 1;
|
|
|
|
[data appendBytes:&len length:sizeof(int)];
|
|
[data appendBytes:[string UTF8String] length:len];
|
|
|
|
[vc sendMessage:AddNewTabMsgID data:nil wait:NO];
|
|
[vc sendMessage:DropStringMsgID data:data wait:NO];
|
|
} else {
|
|
// TODO: Open the selection in the new window.
|
|
*error = @"ERROR: No window found to open selection in.";
|
|
[self newVimWindow:self];
|
|
}
|
|
}
|
|
|
|
- (void)openFile:(NSPasteboard *)pboard userData:(NSString *)userData
|
|
error:(NSString **)error
|
|
{
|
|
if (![[pboard types] containsObject:NSStringPboardType]) {
|
|
NSLog(@"WARNING: Pasteboard contains no object of type "
|
|
"NSStringPboardType");
|
|
return;
|
|
}
|
|
|
|
// TODO: Parse multiple filenames and create array with names.
|
|
int numberOfFiles = 1;
|
|
NSString *string = [pboard stringForType:NSStringPboardType];
|
|
string = [string stringByTrimmingCharactersInSet:
|
|
[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
|
string = [string stringByStandardizingPath];
|
|
|
|
MMVimController *vc = nil;
|
|
if (userData && [userData isEqual:@"Tab"])
|
|
vc = [self topmostVimController];
|
|
|
|
if (vc) {
|
|
NSMutableData *data = [NSMutableData data];
|
|
int len = [string lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
|
|
|
[data appendBytes:&numberOfFiles length:sizeof(int)];
|
|
[data appendBytes:&len length:sizeof(int)];
|
|
[data appendBytes:[string UTF8String] length:len];
|
|
|
|
[vc sendMessage:DropFilesMsgID data:data wait:NO];
|
|
} else {
|
|
[self application:NSApp openFiles:[NSArray arrayWithObject:string]];
|
|
}
|
|
}
|
|
|
|
@end // MMAppController (MMServices)
|
|
|
|
|
|
|
|
|
|
@implementation MMAppController (Private)
|
|
|
|
- (MMVimController *)keyVimController
|
|
{
|
|
NSWindow *keyWindow = [NSApp keyWindow];
|
|
if (keyWindow) {
|
|
unsigned i, count = [vimControllers count];
|
|
for (i = 0; i < count; ++i) {
|
|
MMVimController *vc = [vimControllers objectAtIndex:i];
|
|
if ([[[vc windowController] window] isEqual:keyWindow])
|
|
return vc;
|
|
}
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
- (MMVimController *)topmostVimController
|
|
{
|
|
NSArray *windows = [NSApp orderedWindows];
|
|
if ([windows count] > 0) {
|
|
NSWindow *window = [windows objectAtIndex:0];
|
|
unsigned i, count = [vimControllers count];
|
|
for (i = 0; i < count; ++i) {
|
|
MMVimController *vc = [vimControllers objectAtIndex:i];
|
|
if ([[[vc windowController] window] isEqual:window])
|
|
return vc;
|
|
}
|
|
}
|
|
|
|
return nil;
|
|
}
|
|
|
|
@end
|
|
|
|
|
|
|
|
|
|
@implementation NSMenu (MMExtras)
|
|
|
|
- (void)recurseSetAutoenablesItems:(BOOL)on
|
|
{
|
|
[self setAutoenablesItems:on];
|
|
|
|
int i, count = [self numberOfItems];
|
|
for (i = 0; i < count; ++i) {
|
|
NSMenu *submenu = [[self itemAtIndex:i] submenu];
|
|
if (submenu) {
|
|
[submenu recurseSetAutoenablesItems:on];
|
|
}
|
|
}
|
|
}
|
|
|
|
@end // NSMenu (MMExtras)
|