mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-26 21:03:12 +00:00
Escape separators (colon and equals) to improve completion
Fish completes parts of words split by the separators, so things like
`dd if=/dev/sd<TAB>` work.
This commit improves interactive completion if completion strings legitimately
contain '=' or ':'. Consider this example where completion will suggest
a🅰️1 and other files in the cwd in addition to a:1
touch a:1; complete -C'ls a:'
This behavior remains unchanged, but this commit allows to quote or escape
separators, so that e.g. `ls "a:<TAB>` and `ls a\:<TAB>` successfully complete
the filename.
This also makes the completion insert those escapes automatically unless
already quoted.
So `ls a<TAB>` will give `ls a\:1`.
Both changes match bash's behavior.
This commit is contained in:
parent
54ed2ad440
commit
f7dac82ed6
6 changed files with 24 additions and 13 deletions
|
@ -999,6 +999,7 @@ static void escape_string_script(const wchar_t *orig_in, size_t in_len, wcstring
|
||||||
const bool escape_all = static_cast<bool>(flags & ESCAPE_ALL);
|
const bool escape_all = static_cast<bool>(flags & ESCAPE_ALL);
|
||||||
const bool no_quoted = static_cast<bool>(flags & ESCAPE_NO_QUOTED);
|
const bool no_quoted = static_cast<bool>(flags & ESCAPE_NO_QUOTED);
|
||||||
const bool no_tilde = static_cast<bool>(flags & ESCAPE_NO_TILDE);
|
const bool no_tilde = static_cast<bool>(flags & ESCAPE_NO_TILDE);
|
||||||
|
const bool escape_separators = static_cast<bool>(flags & ESCAPE_SEPARATORS);
|
||||||
const bool no_caret = feature_test(features_t::stderr_nocaret);
|
const bool no_caret = feature_test(features_t::stderr_nocaret);
|
||||||
const bool no_qmark = feature_test(features_t::qmark_noglob);
|
const bool no_qmark = feature_test(features_t::qmark_noglob);
|
||||||
|
|
||||||
|
@ -1098,9 +1099,12 @@ static void escape_string_script(const wchar_t *orig_in, size_t in_len, wcstring
|
||||||
case L';':
|
case L';':
|
||||||
case L'"':
|
case L'"':
|
||||||
case L'%':
|
case L'%':
|
||||||
case L'~': {
|
case L'~':
|
||||||
|
case L':':
|
||||||
|
case L'=': {
|
||||||
bool char_is_normal = (c == L'~' && no_tilde) || (c == L'^' && no_caret) ||
|
bool char_is_normal = (c == L'~' && no_tilde) || (c == L'^' && no_caret) ||
|
||||||
(c == L'?' && no_qmark);
|
(c == L'?' && no_qmark) ||
|
||||||
|
((c == L':' || c == L'=') && !escape_separators);
|
||||||
if (!char_is_normal) {
|
if (!char_is_normal) {
|
||||||
need_escape = 1;
|
need_escape = 1;
|
||||||
if (escape_all) out += L'\\';
|
if (escape_all) out += L'\\';
|
||||||
|
|
|
@ -134,7 +134,9 @@ enum {
|
||||||
/// string.
|
/// string.
|
||||||
ESCAPE_NO_QUOTED = 1 << 1,
|
ESCAPE_NO_QUOTED = 1 << 1,
|
||||||
/// Do not escape tildes.
|
/// Do not escape tildes.
|
||||||
ESCAPE_NO_TILDE = 1 << 2
|
ESCAPE_NO_TILDE = 1 << 2,
|
||||||
|
/// Escape colon and equal sign.
|
||||||
|
ESCAPE_SEPARATORS = 1 << 3,
|
||||||
};
|
};
|
||||||
typedef unsigned int escape_flags_t;
|
typedef unsigned int escape_flags_t;
|
||||||
|
|
||||||
|
|
|
@ -1098,6 +1098,7 @@ void completer_t::complete_param_expand(const wcstring &str, bool do_file,
|
||||||
// Squelch file descriptions per issue #254.
|
// Squelch file descriptions per issue #254.
|
||||||
if (this->type() == COMPLETE_AUTOSUGGEST || do_file) flags |= expand_flag::no_descriptions;
|
if (this->type() == COMPLETE_AUTOSUGGEST || do_file) flags |= expand_flag::no_descriptions;
|
||||||
|
|
||||||
|
// Expand words separated by '=' separately, unless '=' is escaped or quoted.
|
||||||
// We have the following cases:
|
// We have the following cases:
|
||||||
//
|
//
|
||||||
// --foo=bar => expand just bar
|
// --foo=bar => expand just bar
|
||||||
|
@ -1105,14 +1106,16 @@ void completer_t::complete_param_expand(const wcstring &str, bool do_file,
|
||||||
// foo=bar => expand the whole thing, and also just bar
|
// foo=bar => expand the whole thing, and also just bar
|
||||||
//
|
//
|
||||||
// We also support colon separator (#2178). If there's more than one, prefer the last one.
|
// We also support colon separator (#2178). If there's more than one, prefer the last one.
|
||||||
size_t sep_index = str.find_last_of(L"=:");
|
size_t sep_index = str.size();
|
||||||
bool complete_from_separator = (sep_index != wcstring::npos);
|
do {
|
||||||
|
sep_index = sep_index == 0 ? wcstring::npos : str.find_last_of(L"=:", sep_index - 1);
|
||||||
|
} while (sep_index != wcstring::npos && is_backslashed(str, sep_index));
|
||||||
|
wchar_t quote = L'\0';
|
||||||
|
parse_util_get_parameter_info(str, str.size(), "e, NULL, NULL);
|
||||||
|
bool complete_from_separator = (quote == L'\0') && (sep_index != wcstring::npos);
|
||||||
bool complete_from_start = !complete_from_separator || !string_prefixes_string(L"-", str);
|
bool complete_from_start = !complete_from_separator || !string_prefixes_string(L"-", str);
|
||||||
|
|
||||||
if (complete_from_separator) {
|
if (complete_from_separator) {
|
||||||
// FIXME: This just cuts the token,
|
|
||||||
// so any quoting or braces gets lost.
|
|
||||||
// See #4954.
|
|
||||||
const wcstring sep_string = wcstring(str, sep_index + 1);
|
const wcstring sep_string = wcstring(str, sep_index + 1);
|
||||||
std::vector<completion_t> local_completions;
|
std::vector<completion_t> local_completions;
|
||||||
if (expand_string(sep_string, &local_completions, flags, vars, parser, NULL) ==
|
if (expand_string(sep_string, &local_completions, flags, vars, parser, NULL) ==
|
||||||
|
|
|
@ -523,7 +523,8 @@ void parse_util_get_parameter_info(const wcstring &cmd, const size_t pos, wchar_
|
||||||
wcstring parse_util_escape_string_with_quote(const wcstring &cmd, wchar_t quote, bool no_tilde) {
|
wcstring parse_util_escape_string_with_quote(const wcstring &cmd, wchar_t quote, bool no_tilde) {
|
||||||
wcstring result;
|
wcstring result;
|
||||||
if (quote == L'\0') {
|
if (quote == L'\0') {
|
||||||
escape_flags_t flags = ESCAPE_ALL | ESCAPE_NO_QUOTED | (no_tilde ? ESCAPE_NO_TILDE : 0);
|
escape_flags_t flags =
|
||||||
|
ESCAPE_ALL | ESCAPE_NO_QUOTED | (no_tilde ? ESCAPE_NO_TILDE : 0) | ESCAPE_SEPARATORS;
|
||||||
result = escape_string(cmd, flags);
|
result = escape_string(cmd, flags);
|
||||||
} else {
|
} else {
|
||||||
// Here we are going to escape a string with quotes.
|
// Here we are going to escape a string with quotes.
|
||||||
|
|
|
@ -488,7 +488,6 @@ class reader_data_t : public std::enable_shared_from_this<reader_data_t> {
|
||||||
static volatile sig_atomic_t interrupted = 0;
|
static volatile sig_atomic_t interrupted = 0;
|
||||||
|
|
||||||
// Prototypes for a bunch of functions defined later on.
|
// Prototypes for a bunch of functions defined later on.
|
||||||
static bool is_backslashed(const wcstring &str, size_t pos);
|
|
||||||
static wchar_t unescaped_quote(const wcstring &str, size_t pos);
|
static wchar_t unescaped_quote(const wcstring &str, size_t pos);
|
||||||
|
|
||||||
/// Mode on startup, which we restore on exit.
|
/// Mode on startup, which we restore on exit.
|
||||||
|
@ -2297,9 +2296,7 @@ static int can_read(int fd) {
|
||||||
return select(fd + 1, &fds, 0, 0, &can_read_timeout) == 1;
|
return select(fd + 1, &fds, 0, 0, &can_read_timeout) == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Test if the specified character in the specified string is backslashed. pos may be at the end of
|
bool is_backslashed(const wcstring &str, size_t pos) {
|
||||||
/// the string, which indicates if there is a trailing backslash.
|
|
||||||
static bool is_backslashed(const wcstring &str, size_t pos) {
|
|
||||||
// note pos == str.size() is OK.
|
// note pos == str.size() is OK.
|
||||||
if (pos > str.size()) return false;
|
if (pos > str.size()) return false;
|
||||||
|
|
||||||
|
|
|
@ -232,4 +232,8 @@ void reader_bg_job_warning(const parser_t &parser);
|
||||||
/// been executed between invocations of code.
|
/// been executed between invocations of code.
|
||||||
uint64_t reader_run_count();
|
uint64_t reader_run_count();
|
||||||
|
|
||||||
|
/// Test if the specified character in the specified string is backslashed. pos may be at the end of
|
||||||
|
/// the string, which indicates if there is a trailing backslash.
|
||||||
|
bool is_backslashed(const wcstring &str, size_t pos);
|
||||||
|
|
||||||
#endif
|
#endif
|
||||||
|
|
Loading…
Reference in a new issue