core: don't force shell integration unless we're running that shell
If you bypassed shell detection and forced a specific shell integration with `shell-integration=<shell>` the `bash` and `nushell` integrations would break commands that were specified with `+new-window -e <command>` (or a similar mechanism) because they modify the command in shell-specific ways and the new command would fail with an "unknown flag" error or similar. This PR fixes that by only permitting shell integration if the command matches the type of shell integration. That means that `shell-integration=nushell` only works if `argv[0]` ends with `nu`, etc. Generally this should not matter unless the shell executable was renamed for some reason and the shell detection failed. Fixes #12378pull/12390/head
parent
2a3d93f77b
commit
103babecc2
|
|
@ -2792,7 +2792,10 @@ keybind: Keybinds = .{},
|
|||
///
|
||||
/// * `detect` - Detect the shell based on the filename.
|
||||
///
|
||||
/// * `bash`, `elvish`, `fish`, `nushell`, `zsh` - Use this specific shell injection scheme.
|
||||
/// * `bash`, `elvish`, `fish`, `nushell`, `zsh` - Use this specific shell
|
||||
/// injection scheme if the command is this shell. Has no effect if the
|
||||
/// command is not a shell, or is a different shell from the one specified
|
||||
/// here.
|
||||
///
|
||||
/// The default value is `detect`.
|
||||
@"shell-integration": ShellIntegration = .detect,
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
const std = @import("std");
|
||||
const builtin = @import("builtin");
|
||||
|
||||
const Allocator = std.mem.Allocator;
|
||||
const ArenaAllocator = std.heap.ArenaAllocator;
|
||||
const formatterpkg = @import("formatter.zig");
|
||||
|
|
@ -197,6 +199,87 @@ pub const Command = union(enum) {
|
|||
}
|
||||
}
|
||||
|
||||
/// Shell types we support
|
||||
pub const Shell = enum {
|
||||
bash,
|
||||
elvish,
|
||||
fish,
|
||||
nushell,
|
||||
zsh,
|
||||
};
|
||||
|
||||
/// Detect if this command is a known shell.
|
||||
pub fn detectShell(self: Self) ?Shell {
|
||||
var buf: [std.fs.max_path_bytes]u8 = undefined;
|
||||
var fba: std.heap.FixedBufferAllocator = .init(&buf);
|
||||
|
||||
const arg0 = switch (self) {
|
||||
.direct => |v| v[0],
|
||||
.shell => |v| arg: {
|
||||
var it = std.process.ArgIteratorGeneral(.{}).init(fba.allocator(), v) catch return null;
|
||||
break :arg it.next() orelse return null;
|
||||
},
|
||||
};
|
||||
|
||||
const exe = std.fs.path.basename(arg0);
|
||||
|
||||
if (std.mem.eql(u8, "bash", exe)) {
|
||||
// Apple distributes their own patched version of Bash 3.2
|
||||
// on macOS that disables the ENV-based POSIX startup path.
|
||||
// This means we're unable to perform our automatic shell
|
||||
// integration sequence in this specific environment.
|
||||
//
|
||||
// If we're running "/bin/bash" on Darwin, we can assume
|
||||
// we're using Apple's Bash because /bin is non-writable
|
||||
// on modern macOS due to System Integrity Protection.
|
||||
if (comptime builtin.target.os.tag.isDarwin()) {
|
||||
if (std.mem.eql(u8, "/bin/bash", arg0)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return .bash;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, "elvish", exe)) return .elvish;
|
||||
if (std.mem.eql(u8, "fish", exe)) return .fish;
|
||||
if (std.mem.eql(u8, "nu", exe)) return .nushell;
|
||||
if (std.mem.eql(u8, "zsh", exe)) return .zsh;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
test detectShell {
|
||||
const testing = std.testing;
|
||||
|
||||
try testing.expect(detectShell(.{ .shell = "sh" }) == null);
|
||||
try testing.expectEqual(.bash, detectShell(.{ .shell = "bash" }));
|
||||
try testing.expectEqual(.elvish, detectShell(.{ .shell = "elvish" }));
|
||||
try testing.expectEqual(.fish, detectShell(.{ .shell = "fish" }));
|
||||
try testing.expectEqual(.nushell, detectShell(.{ .shell = "nu" }));
|
||||
try testing.expectEqual(.zsh, detectShell(.{ .shell = "zsh" }));
|
||||
|
||||
if (comptime builtin.target.os.tag.isDarwin()) {
|
||||
try testing.expect(detectShell(.{ .shell = "/bin/bash" }) == null);
|
||||
}
|
||||
|
||||
try testing.expectEqual(.bash, detectShell(.{ .shell = "bash -c 'command'" }));
|
||||
try testing.expectEqual(.bash, detectShell(.{ .shell = "\"/a b/bash\"" }));
|
||||
|
||||
try testing.expect(detectShell(.{ .direct = &.{"sh"} }) == null);
|
||||
try testing.expectEqual(.bash, detectShell(.{ .direct = &.{"bash"} }));
|
||||
try testing.expectEqual(.elvish, detectShell(.{ .direct = &.{"elvish"} }));
|
||||
try testing.expectEqual(.fish, detectShell(.{ .direct = &.{"fish"} }));
|
||||
try testing.expectEqual(.nushell, detectShell(.{ .direct = &.{"nu"} }));
|
||||
try testing.expectEqual(.zsh, detectShell(.{ .direct = &.{"zsh"} }));
|
||||
|
||||
if (comptime builtin.target.os.tag.isDarwin()) {
|
||||
try testing.expect(detectShell(.{&.{ .direct = "/bin/bash" }}) == null);
|
||||
}
|
||||
|
||||
try testing.expectEqual(.bash, detectShell(.{ .direct = &.{ "bash", "-c", "command" } }));
|
||||
try testing.expectEqual(.bash, detectShell(.{ .direct = &.{"/a b/bash"} }));
|
||||
}
|
||||
|
||||
test "Command: parseCLI errors" {
|
||||
const testing = std.testing;
|
||||
var arena = ArenaAllocator.init(testing.allocator);
|
||||
|
|
|
|||
|
|
@ -10,13 +10,7 @@ const internal_os = @import("../os/main.zig");
|
|||
const log = std.log.scoped(.shell_integration);
|
||||
|
||||
/// Shell types we support
|
||||
pub const Shell = enum {
|
||||
bash,
|
||||
elvish,
|
||||
fish,
|
||||
nushell,
|
||||
zsh,
|
||||
};
|
||||
pub const Shell = config.Command.Shell;
|
||||
|
||||
/// The result of setting up a shell integration.
|
||||
pub const ShellIntegration = struct {
|
||||
|
|
@ -46,10 +40,21 @@ pub fn setup(
|
|||
env: *EnvMap,
|
||||
force_shell: ?Shell,
|
||||
) !?ShellIntegration {
|
||||
const actual_shell = command.detectShell();
|
||||
const shell: Shell = force_shell orelse
|
||||
try detectShell(alloc_arena, command) orelse
|
||||
actual_shell orelse
|
||||
return null;
|
||||
|
||||
// Don't do any shell integration if we're not actually running the shell.
|
||||
// This prevents problems when the command is overridden by `+new-window`
|
||||
// or some other mechanism and `shell-integration` is forced to a specific
|
||||
// shell rather than being detected.
|
||||
//
|
||||
// This means that `shell-integration=<shell>` has no effect if we detect
|
||||
// that the command is not a shell, or is not the shell specified by
|
||||
// `shell-integration`.
|
||||
if (shell != actual_shell) return null;
|
||||
|
||||
const new_command: config.Command = switch (shell) {
|
||||
.bash => try setupBash(
|
||||
alloc_arena,
|
||||
|
|
@ -107,7 +112,7 @@ test "force shell" {
|
|||
&env,
|
||||
shell,
|
||||
);
|
||||
try testing.expectEqual(shell, result.?.shell);
|
||||
try testing.expect(result == null);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -133,57 +138,6 @@ test "shell integration failure" {
|
|||
try testing.expectEqual(0, env.count());
|
||||
}
|
||||
|
||||
fn detectShell(alloc: Allocator, command: config.Command) !?Shell {
|
||||
var arg_iter = try command.argIterator(alloc);
|
||||
defer arg_iter.deinit();
|
||||
|
||||
const arg0 = arg_iter.next() orelse return null;
|
||||
const exe = std.fs.path.basename(arg0);
|
||||
|
||||
if (std.mem.eql(u8, "bash", exe)) {
|
||||
// Apple distributes their own patched version of Bash 3.2
|
||||
// on macOS that disables the ENV-based POSIX startup path.
|
||||
// This means we're unable to perform our automatic shell
|
||||
// integration sequence in this specific environment.
|
||||
//
|
||||
// If we're running "/bin/bash" on Darwin, we can assume
|
||||
// we're using Apple's Bash because /bin is non-writable
|
||||
// on modern macOS due to System Integrity Protection.
|
||||
if (comptime builtin.target.os.tag.isDarwin()) {
|
||||
if (std.mem.eql(u8, "/bin/bash", arg0)) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
return .bash;
|
||||
}
|
||||
|
||||
if (std.mem.eql(u8, "elvish", exe)) return .elvish;
|
||||
if (std.mem.eql(u8, "fish", exe)) return .fish;
|
||||
if (std.mem.eql(u8, "nu", exe)) return .nushell;
|
||||
if (std.mem.eql(u8, "zsh", exe)) return .zsh;
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
test detectShell {
|
||||
const testing = std.testing;
|
||||
const alloc = testing.allocator;
|
||||
|
||||
try testing.expect(try detectShell(alloc, .{ .shell = "sh" }) == null);
|
||||
try testing.expectEqual(.bash, try detectShell(alloc, .{ .shell = "bash" }));
|
||||
try testing.expectEqual(.elvish, try detectShell(alloc, .{ .shell = "elvish" }));
|
||||
try testing.expectEqual(.fish, try detectShell(alloc, .{ .shell = "fish" }));
|
||||
try testing.expectEqual(.nushell, try detectShell(alloc, .{ .shell = "nu" }));
|
||||
try testing.expectEqual(.zsh, try detectShell(alloc, .{ .shell = "zsh" }));
|
||||
|
||||
if (comptime builtin.target.os.tag.isDarwin()) {
|
||||
try testing.expect(try detectShell(alloc, .{ .shell = "/bin/bash" }) == null);
|
||||
}
|
||||
|
||||
try testing.expectEqual(.bash, try detectShell(alloc, .{ .shell = "bash -c 'command'" }));
|
||||
try testing.expectEqual(.bash, try detectShell(alloc, .{ .shell = "\"/a b/bash\"" }));
|
||||
}
|
||||
|
||||
/// Set up the shell integration features environment variable.
|
||||
pub fn setupFeatures(
|
||||
env: *EnvMap,
|
||||
|
|
|
|||
Loading…
Reference in New Issue