mirror of
https://github.com/neovim/neovim.git
synced 2026-08-29 02:21:51 +00:00
fix(option): set 'shell…' options based on detected shell #40031
Problem: * 'shellcmdflag' states that its default value is set according to the value of 'shell', but this behavior is not yet implemented on Windows. The same applies to 'shellpipe', 'shellredir', and 'shellxquote'. * On Windows, Git is often installed in paths containing spaces, and we still do not correctly resolve the sh executable name as described in 'shell'. * On Windows, the default value of 'shellslash' is always `false`, which causes Unix-like shells to interpret `\` in paths returned by some functions as escape charaters. Solution: Use a simple rule table to detect common shells (e.g. `cmd`, `powershell`, shells whose names contain `csh` or `sh`) and apply best-effort defaults, while leaving more complex scenarios to user configuration.
This commit is contained in:
@@ -626,44 +626,57 @@ void set_init_2(bool headless)
|
||||
change_option_default(kOptWindow, NUMBER_OPTVAL(Rows - 1));
|
||||
}
|
||||
|
||||
static const struct {
|
||||
const char *pat;
|
||||
const char *shcf;
|
||||
const char *sp;
|
||||
const char *srr;
|
||||
const char *sxq;
|
||||
} shell_rules[] = {
|
||||
#ifdef MSWIN
|
||||
{ "cmd", "/s /c", NULL, NULL, "\"" },
|
||||
{ "powershell", "-Command", NULL, NULL, NULL },
|
||||
#endif
|
||||
{ "csh", NULL, "|& tee", ">&", NULL },
|
||||
{ "sh", NULL, "2>&1| tee", ">%s 2>&1", NULL },
|
||||
};
|
||||
|
||||
static void change_option_and_default_if_unset(OptIndex idx, const char *val)
|
||||
{
|
||||
if (val == NULL || options[idx].flags & kOptFlagWasSet) {
|
||||
return;
|
||||
}
|
||||
OptVal optval = CSTR_AS_OPTVAL(val);
|
||||
set_option_direct(idx, optval, 0, SID_NONE);
|
||||
change_option_default(idx, optval_copy(optval));
|
||||
}
|
||||
|
||||
/// Initialize the options, part three: After reading the .vimrc
|
||||
void set_init_3(void)
|
||||
{
|
||||
parse_shape_opt(SHAPE_CURSOR); // set cursor shapes from 'guicursor'
|
||||
|
||||
// Set 'shellpipe' and 'shellredir', depending on the 'shell' option.
|
||||
// This is done after other initializations, where 'shell' might have been
|
||||
// set, but only if they have not been set before.
|
||||
bool do_srr = !(options[kOptShellredir].flags & kOptFlagWasSet);
|
||||
bool do_sp = !(options[kOptShellpipe].flags & kOptFlagWasSet);
|
||||
|
||||
size_t len = 0;
|
||||
char *p = (char *)invocation_path_tail(p_sh, &len);
|
||||
p = xmemdupz(p, len);
|
||||
|
||||
bool is_csh = path_fnamecmp(p, "csh") == 0 || path_fnamecmp(p, "tcsh") == 0;
|
||||
bool is_known_shell = path_fnamecmp(p, "sh") == 0 || path_fnamecmp(p, "ksh") == 0
|
||||
|| path_fnamecmp(p, "mksh") == 0 || path_fnamecmp(p, "pdksh") == 0
|
||||
|| path_fnamecmp(p, "zsh") == 0 || path_fnamecmp(p, "zsh-beta") == 0
|
||||
|| path_fnamecmp(p, "bash") == 0 || path_fnamecmp(p, "fish") == 0
|
||||
|| path_fnamecmp(p, "ash") == 0 || path_fnamecmp(p, "dash") == 0;
|
||||
|
||||
// Default for p_sp is "| tee", for p_srr is ">".
|
||||
// For known shells it is changed here to include stderr.
|
||||
if (is_csh || is_known_shell) {
|
||||
if (do_sp) {
|
||||
const OptVal sp =
|
||||
is_csh ? STATIC_CSTR_AS_OPTVAL("|& tee") : STATIC_CSTR_AS_OPTVAL("2>&1| tee");
|
||||
set_option_direct(kOptShellpipe, sp, 0, SID_NONE);
|
||||
change_option_default(kOptShellpipe, optval_copy(sp));
|
||||
size_t len;
|
||||
char name[MAXPATHL];
|
||||
const char *p = invocation_path_tail(p_sh, &len);
|
||||
xmemcpyz(name, p, len);
|
||||
for (size_t i = 0; i < ARRAY_SIZE(shell_rules); i++) {
|
||||
if (strstr(name, shell_rules[i].pat) == NULL) {
|
||||
continue;
|
||||
}
|
||||
if (do_srr) {
|
||||
const OptVal srr = is_csh ? STATIC_CSTR_AS_OPTVAL(">&") : STATIC_CSTR_AS_OPTVAL(">%s 2>&1");
|
||||
set_option_direct(kOptShellredir, srr, 0, SID_NONE);
|
||||
change_option_default(kOptShellredir, optval_copy(srr));
|
||||
change_option_and_default_if_unset(kOptShellcmdflag, shell_rules[i].shcf);
|
||||
change_option_and_default_if_unset(kOptShellpipe, shell_rules[i].sp);
|
||||
change_option_and_default_if_unset(kOptShellredir, shell_rules[i].srr);
|
||||
change_option_and_default_if_unset(kOptShellxquote, shell_rules[i].sxq);
|
||||
#ifdef MSWIN
|
||||
if (i > 0 && !(options[kOptShellslash].flags & kOptFlagWasSet)) {
|
||||
// Use `/` as path separator on Unix-like shells or powershell on Windows
|
||||
set_option_direct(kOptShellslash, BOOLEAN_OPTVAL(true), 0, SID_NONE);
|
||||
change_option_default(kOptShellslash, BOOLEAN_OPTVAL(true));
|
||||
}
|
||||
#endif
|
||||
break;
|
||||
}
|
||||
xfree(p);
|
||||
|
||||
if (buf_is_empty(curbuf)) {
|
||||
// Apply the first entry of 'fileformats' to the initial buffer.
|
||||
|
||||
@@ -7709,10 +7709,9 @@ local options = {
|
||||
{
|
||||
abbreviation = 'shcf',
|
||||
defaults = {
|
||||
condition = 'MSWIN',
|
||||
if_false = '-c',
|
||||
if_true = '/s /c',
|
||||
doc = '"-c"; Windows: "/s /c"',
|
||||
if_true = '-c',
|
||||
doc = [["-c"; Windows, when 'shell'
|
||||
contains "cmd" somewhere: "/s /c"]],
|
||||
},
|
||||
desc = [=[
|
||||
Flag passed to the shell to execute "!" and ":!" commands; e.g.,
|
||||
@@ -7751,12 +7750,12 @@ local options = {
|
||||
For MS-Windows the default is "2>&1| tee". The stdout and stderr are
|
||||
saved in a file and echoed to the screen.
|
||||
For Unix the default is "| tee". The stdout of the compiler is saved
|
||||
in a file and echoed to the screen. If the 'shell' option is "csh" or
|
||||
"tcsh" after initializations, the default becomes "|& tee". If the
|
||||
'shell' option is "sh", "ksh", "mksh", "pdksh", "zsh", "zsh-beta",
|
||||
"bash", "fish", "ash" or "dash" the default becomes "2>&1| tee". This
|
||||
means that stderr is also included. Before using the 'shell' option a
|
||||
path is removed, thus "/bin/sh" uses "sh".
|
||||
in a file and echoed to the screen. If the 'shell' option contains
|
||||
"csh" (e.g. "tcsh") after initializations, the default becomes
|
||||
"|& tee". Otherwise, if it contains "sh" (e.g. "bash", "zsh"), the
|
||||
default becomes "2>&1| tee". This means that stderr is also included.
|
||||
Before using the 'shell' option a path is removed, thus "/bin/sh" uses
|
||||
"sh".
|
||||
The initialization of this option is done after reading the vimrc
|
||||
and the other initializations, so that when the 'shell' option is set
|
||||
there, the 'shellpipe' option changes automatically, unless it was
|
||||
@@ -7821,12 +7820,12 @@ local options = {
|
||||
The name of the temporary file can be represented by "%s" if necessary
|
||||
(the file name is appended automatically if no %s appears in the value
|
||||
of this option).
|
||||
The default is ">". For Unix, if the 'shell' option is "csh" or
|
||||
"tcsh" during initializations, the default becomes ">&". If the
|
||||
'shell' option is "sh", "ksh", "mksh", "pdksh", "zsh", "zsh-beta",
|
||||
"bash" or "fish", the default becomes ">%s 2>&1". This means that
|
||||
stderr is also included. For Win32, the Unix checks are done and
|
||||
additionally "cmd" is checked for, which makes the default ">%s 2>&1".
|
||||
The default is ">". For Unix, if the 'shell' option contains "csh"
|
||||
(e.g. "tcsh") during initializations, the default becomes ">&".
|
||||
Otherwise, if it contains "sh" (e.g. "bash", "zsh"), the default
|
||||
becomes ">%s 2>&1". This means that stderr is also included. For
|
||||
Win32, the Unix checks are done and additionally "cmd" is checked
|
||||
for, which makes the default ">%s 2>&1".
|
||||
Also, the same names with ".exe" appended are checked for.
|
||||
The initialization of this option is done after reading the vimrc
|
||||
and the other initializations, so that when the 'shell' option is set
|
||||
@@ -7851,7 +7850,8 @@ local options = {
|
||||
condition = 'MSWIN',
|
||||
if_true = false,
|
||||
if_false = true,
|
||||
doc = 'on, Windows: off',
|
||||
doc = [[on; Windows: off, except when 'shell'
|
||||
contains "sh" somewhere]],
|
||||
},
|
||||
desc = [=[
|
||||
only modifiable in MS-Windows
|
||||
@@ -7913,10 +7913,9 @@ local options = {
|
||||
{
|
||||
abbreviation = 'sxq',
|
||||
defaults = {
|
||||
condition = 'MSWIN',
|
||||
if_false = '',
|
||||
if_true = '"',
|
||||
doc = '"", Windows: "\\""',
|
||||
if_true = '',
|
||||
doc = [[""; Windows, when 'shell'
|
||||
contains "cmd" somewhere: "\""]],
|
||||
},
|
||||
desc = [=[
|
||||
Quoting character(s), put around the command passed to the shell, for
|
||||
|
||||
@@ -139,10 +139,24 @@ char *path_tail_with_sep(char *fname)
|
||||
return tail;
|
||||
}
|
||||
|
||||
/// Finds the path tail (or executable) in an invocation.
|
||||
/// Finds the executable name (path tail) in a program invocation.
|
||||
///
|
||||
/// @param[in] invocation A program invocation in the form:
|
||||
/// "path/to/exe [args]".
|
||||
/// The invocation starts with an executable path, optionally followed
|
||||
/// by arguments.
|
||||
///
|
||||
/// Parsing rules:
|
||||
/// - A space outside double quotes ends the executable path.
|
||||
/// - Within quoted segments, a backslash skips the following character.
|
||||
/// Note: on Windows, `\` is treated firstly as a path separator. In
|
||||
/// practice, this rule should be rarely needed anyway.
|
||||
///
|
||||
/// Examples:
|
||||
/// - "path/foo/bash --login" => "bash"
|
||||
/// - "path/foo bar/bash --login" => "foo"
|
||||
/// - "\"path/foo bar/bash\" --login" => "bash"
|
||||
/// - "\"path/foo\\\" bar/bash\" --login" => "bash"
|
||||
///
|
||||
/// @param[in] invocation Program invocation string.
|
||||
/// @param[out] len Stores the length of the executable name.
|
||||
///
|
||||
/// @post if `len` is not null, stores the length of the executable name.
|
||||
@@ -152,17 +166,25 @@ const char *invocation_path_tail(const char *invocation, size_t *len)
|
||||
FUNC_ATTR_NONNULL_RET FUNC_ATTR_NONNULL_ARG(1)
|
||||
{
|
||||
const char *tail = get_past_head(invocation);
|
||||
const char *tail_end = tail;
|
||||
const char *p = tail;
|
||||
while (*p != NUL && *p != ' ') {
|
||||
bool was_sep = vim_ispathsep_nocolon(*p);
|
||||
MB_PTR_ADV(p);
|
||||
if (was_sep) {
|
||||
tail = p; // Now tail points one past the separator.
|
||||
bool inquote = false;
|
||||
while (*p != NUL && (inquote || *p != ' ')) {
|
||||
int l = utfc_ptr2len(p);
|
||||
if (vim_ispathsep_nocolon(*p)) {
|
||||
tail = p + 1; // Now tail points one past the separator.
|
||||
} else if (*p == '\\' && inquote) {
|
||||
p++;
|
||||
} else if (*p == '"') {
|
||||
inquote ^= 1;
|
||||
} else {
|
||||
tail_end = p + l;
|
||||
}
|
||||
p += l;
|
||||
}
|
||||
|
||||
if (len != NULL) {
|
||||
*len = (size_t)(p - tail);
|
||||
*len = (size_t)(tail_end - tail);
|
||||
}
|
||||
|
||||
return tail;
|
||||
|
||||
Reference in New Issue
Block a user