Improved method to start Vim processes in a login shell

A login shell is started by exec'ing a shell process whose argv[0] is
prepended with a dash.  Which shell to use can be controlled with user
default MMLoginShellCommand (or by setting $SHELL).  If the shell needs
a parameter to make it a login shell then this can be set with the user
default MMLoginShellArgument.  The documentation has been updated with
these new user defaults.
This commit is contained in:
Ben Schmidt
2008-03-07 22:42:46 +01:00
committed by Bjorn Winckler
parent 12bec6e4ef
commit 3d8abd2669
4 changed files with 159 additions and 50 deletions
+19
View File
@@ -221,6 +221,8 @@ Here is a list of relevant dictionary entries:
KEY VALUE ~
MMCellWidthMultiplier width of a normal glyph in em units [float]
MMLoginShellArgument login shell parameter [string]
MMLoginShellCommand which shell to use to launch Vim [string]
MMNoFontSubstitution disable automatic font substitution [bool]
MMTabMaxWidth maximum width of a tab [int]
MMTabMinWidth minimum width of a tab [int]
@@ -241,6 +243,23 @@ command is: >
If you wish to restore all user defaults to their starting values, open
Terminal and type: >
defaults delete org.vim.MacVim
<
*macvim-login-shell*
Applications opened from the Finder do not automatically source the user's
environment variables (which are typically set in .profile or .bashrc). This
presents a problem when using |:!| to execute commands in the shell since e.g.
$PATH might not be set properly. To work around this problem MacVim can start
new Vim processes via a login shell so that all environment variables are set.
By default MacVim uses the $SHELL environment variable to determine which
shell to use (if $SHELL is not set "/bin/bash" is used). It is possible to
override this choice by setting the user default MMLoginShellCommand to the
shell that should be used (e.g. "/bin/tcsh"). MacVim tries to make the shell
a login shell by prepending argv[0] with a dash. If you use an exotic shell
and need to pass it a parameter to make it a login shell then you can set the
user default MMLoginShellArgument (e.g. to "-l"). Finally, if the "bash"
shell is used, then "-l" is automatically added as an argument. To override
this behaviour set MMLoginShellArgument to "--".
==============================================================================
4. Special colors *macvim-colors*
+136 -50
View File
@@ -30,6 +30,7 @@
#import "MMVimController.h"
#import "MMWindowController.h"
#import "MMPreferenceController.h"
#import <unistd.h>
#define MM_HANDLE_XCODE_MOD_EVENT 0
@@ -54,6 +55,9 @@ typedef struct
#pragma options align=reset
static int executeInLoginShell(NSString *path, NSArray *args);
@interface MMAppController (MMServices)
- (void)openSelection:(NSPasteboard *)pboard userData:(NSString *)userData
error:(NSString **)error;
@@ -116,6 +120,8 @@ typedef struct
MMUntitledWindowKey,
[NSNumber numberWithBool:NO], MMTexturedWindowKey,
[NSNumber numberWithBool:NO], MMZoomBothKey,
@"", MMLoginShellCommandKey,
@"", MMLoginShellArgumentKey,
nil];
[[NSUserDefaults standardUserDefaults] registerDefaults:dict];
@@ -274,6 +280,13 @@ typedef struct
pid = [self launchVimProcessWithArguments:fileArgs];
if (-1 == pid) {
// TODO: Notify user of failure?
[NSApp replyToOpenOrPrint:
NSApplicationDelegateReplyFailure];
return;
}
// Make sure these files aren't opened again when
// connectBackend:pid: is called.
[arguments setObject:[NSNumber numberWithBool:NO]
@@ -736,68 +749,43 @@ typedef struct
- (int)launchVimProcessWithArguments:(NSArray *)args
{
NSString *taskPath = nil;
NSArray *taskArgs = nil;
int pid = -1;
NSString *path = [[NSBundle mainBundle] pathForAuxiliaryExecutable:@"Vim"];
if (!path) {
NSLog(@"ERROR: Vim executable could not be found inside app bundle!");
return 0;
return -1;
}
if ([[NSUserDefaults standardUserDefaults] boolForKey:MMLoginShellKey]) {
// Run process with a login shell
// $SHELL -l -c "exec Vim -g -f args"
// (-g for GUI, -f for foreground, i.e. don't fork)
NSArray *taskArgs = [NSArray arrayWithObjects:@"-g", @"-f", nil];
if (args)
taskArgs = [taskArgs arrayByAddingObjectsFromArray:args];
NSMutableString *execArg = [NSMutableString
stringWithFormat:@"exec \"%@\" -g -f", path];
if (args) {
// Append all arguments while making sure that arguments containing
// spaces are enclosed in quotes.
NSCharacterSet *space = [NSCharacterSet whitespaceCharacterSet];
unsigned i, count = [args count];
for (i = 0; i < count; ++i) {
NSString *arg = [args objectAtIndex:i];
if (NSNotFound != [arg rangeOfCharacterFromSet:space].location)
[execArg appendFormat:@" \"%@\"", arg];
else
[execArg appendFormat:@" %@", arg];
}
}
// Launch the process with a login shell so that users environment
// settings get sourced. This does not always happen when MacVim is
// started.
taskArgs = [NSArray arrayWithObjects:@"-l", @"-c", execArg, nil];
taskPath = [[[NSProcessInfo processInfo] environment]
objectForKey:@"SHELL"];
if (!taskPath)
taskPath = @"/bin/sh";
BOOL useLoginShell = [[NSUserDefaults standardUserDefaults]
boolForKey:MMLoginShellKey];
if (useLoginShell) {
// Run process with a login shell, roughly:
// echo "exec Vim -g -f args" | ARGV0=-`basename $SHELL` $SHELL [-l]
pid = executeInLoginShell(path, taskArgs);
} else {
// Run process directly:
// Vim -g -f args
// (-g for GUI, -f for foreground, i.e. don't fork)
taskPath = path;
taskArgs = [NSArray arrayWithObjects:@"-g", @"-f", nil];
if (args)
taskArgs = [taskArgs arrayByAddingObjectsFromArray:args];
NSTask *task = [NSTask launchedTaskWithLaunchPath:path
arguments:taskArgs];
pid = task ? [task processIdentifier] : -1;
}
NSTask *task =[NSTask launchedTaskWithLaunchPath:taskPath
arguments:taskArgs];
//NSLog(@"launch %@ with args=%@ (pid=%d)", taskPath, taskArgs,
// [task processIdentifier]);
int pid = [task processIdentifier];
// If the process has no arguments, then add a null argument to the
// pidArguments dictionary. This is later used to detect that a process
// without arguments is being launched.
if (!args)
[pidArguments setObject:[NSNull null]
forKey:[NSNumber numberWithInt:pid]];
if (-1 != pid) {
// NOTE: If the process has no arguments, then add a null argument to
// the pidArguments dictionary. This is later used to detect that a
// process without arguments is being launched.
if (!args)
[pidArguments setObject:[NSNull null]
forKey:[NSNumber numberWithInt:pid]];
} else {
NSLog(@"WARNING: %s%@ failed (useLoginShell=%d)", _cmd, args,
useLoginShell);
}
return pid;
}
@@ -1099,3 +1087,101 @@ typedef struct
return [self intValue];
}
@end // NSNumber (MMExtras)
static int
executeInLoginShell(NSString *path, NSArray *args)
{
// Start a login shell and execute the command 'path' with arguments 'args'
// in the shell. This ensures that user environment variables are set even
// when MacVim was started from the Finder.
int pid = -1;
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
// Determine which shell to use to execute the command. The user
// may decide which shell to use by setting a user default or the
// $SHELL environment variable.
NSString *shell = [ud stringForKey:MMLoginShellCommandKey];
if (!shell || [shell length] == 0)
shell = [[[NSProcessInfo processInfo] environment]
objectForKey:@"SHELL"];
if (!shell)
shell = @"/bin/bash";
//NSLog(@"shell = %@", shell);
// Bash needs the '-l' flag to launch a login shell. The user may add
// flags by setting a user default.
NSString *shellArgument = [ud stringForKey:MMLoginShellArgumentKey];
if (!shellArgument || [shellArgument length] == 0) {
if ([[shell lastPathComponent] isEqual:@"bash"])
shellArgument = @"-l";
else
shellArgument = nil;
}
//NSLog(@"shellArgument = %@", shellArgument);
// Build input string to pipe to the login shell.
NSMutableString *input = [NSMutableString stringWithFormat:
@"exec \"%@\"", path];
if (args) {
// Append all arguments, making sure they are properly quoted, even
// when they contain single quotes.
NSEnumerator *e = [args objectEnumerator];
id obj;
while ((obj = [e nextObject])) {
NSMutableString *arg = [NSMutableString stringWithString:obj];
[arg replaceOccurrencesOfString:@"'" withString:@"'\"'\"'"
options:NSLiteralSearch
range:NSMakeRange(0, [arg length])];
[input appendFormat:@" '%@'", arg];
}
}
// Build the argument vector used to start the login shell.
NSString *shellArg0 = [NSString stringWithFormat:@"-%@",
[shell lastPathComponent]];
char *shellArgv[3] = { (char *)[shellArg0 UTF8String], NULL, NULL };
if (shellArgument)
shellArgv[1] = (char *)[shellArgument UTF8String];
// Get the C string representation of the shell path before the fork since
// we must not call Foundation functions after a fork.
char *shellPath = [shell fileSystemRepresentation];
// Fork and execute the process.
int ds[2];
if (pipe(ds)) return -1;
pid = fork();
if (pid == -1) {
return -1;
} else if (pid == 0) {
// Child process
if (close(ds[1]) == -1) exit(255);
if (dup2(ds[0], 0) == -1) exit(255);
execv(shellPath, shellArgv);
// Never reached unless execv fails
exit(255);
} else {
// Parent process
if (close(ds[0]) == -1) return -1;
// Send input to execute to the child process
[input appendString:@"\n"];
int bytes = [input lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
if (write(ds[1], [input UTF8String], bytes) != bytes) return -1;
if (close(ds[1]) == -1) return -1;
}
return pid;
}
+2
View File
@@ -222,6 +222,8 @@ extern NSString *MMUntitledWindowKey;
extern NSString *MMTexturedWindowKey;
extern NSString *MMZoomBothKey;
extern NSString *MMCurrentPreferencePaneKey;
extern NSString *MMLoginShellCommandKey;
extern NSString *MMLoginShellArgumentKey;
// Enum for MMUntitledWindowKey
enum {
+2
View File
@@ -102,6 +102,8 @@ NSString *MMUntitledWindowKey = @"MMUntitledWindow";
NSString *MMTexturedWindowKey = @"MMTexturedWindow";
NSString *MMZoomBothKey = @"MMZoomBoth";
NSString *MMCurrentPreferencePaneKey = @"MMCurrentPreferencePane";
NSString *MMLoginShellCommandKey = @"MMLoginShellCommand";
NSString *MMLoginShellArgumentKey = @"MMLoginShellArgument";