Rewrite wildcard_complete_internal to be easier to follow

This commit is contained in:
ridiculousfish 2015-08-03 15:51:27 -07:00
parent f0a2f24701
commit 83322f63c6

View File

@ -80,6 +80,19 @@ wildcards using **.
*/ */
#define COMPLETE_DIRECTORY_DESC _( L"Directory" ) #define COMPLETE_DIRECTORY_DESC _( L"Directory" )
/* Finds an internal (ANY_STRING, etc.) style wildcard, or wcstring::npos */
static size_t wildcard_find(const wchar_t *wc)
{
for (size_t i=0; wc[i] != L'\0'; i++)
{
if (wc[i] == ANY_CHAR || wc[i] == ANY_STRING || wc[i] == ANY_STRING_RECURSIVE)
{
return i;
}
}
return wcstring::npos;
}
// Implementation of wildcard_has. Needs to take the length to handle embedded nulls (#1631) // Implementation of wildcard_has. Needs to take the length to handle embedded nulls (#1631)
static bool wildcard_has_impl(const wchar_t *str, size_t len, bool internal) static bool wildcard_has_impl(const wchar_t *str, size_t len, bool internal)
{ {
@ -189,129 +202,154 @@ static bool wildcard_match_internal(const wchar_t *str, const wchar_t *wc, bool
return false; return false;
} }
/* This does something horrible refactored from an even more horrible function */
static wcstring resolve_description(wcstring *completion, const wchar_t *explicit_desc, wcstring(*desc_func)(const wcstring &))
{
size_t complete_sep_loc = completion->find(PROG_COMPLETE_SEP);
if (complete_sep_loc != wcstring::npos)
{
/* This completion has an embedded description, do not use the generic description */
const wcstring description = completion->substr(complete_sep_loc + 1);
completion->resize(complete_sep_loc);
return description;
}
else
{
const wcstring func_result = (desc_func ? desc_func(*completion) : wcstring());
if (! func_result.empty())
{
return func_result;
}
else
{
return explicit_desc ? explicit_desc : L"";
}
}
}
/* A transient parameter pack needed by wildcard_complete.f */
struct wc_complete_pack_t
{
const wcstring &orig; // the original string, transient
const wchar_t *desc; // literal description
wcstring(*desc_func)(const wcstring &); // function for generating descriptions
expand_flags_t expand_flags;
wc_complete_pack_t(const wcstring &str) : orig(str) {}
};
/** /**
Matches the string against the wildcard, and if the wildcard is a Matches the string against the wildcard, and if the wildcard is a
possible completion of the string, the remainder of the string is possible completion of the string, the remainder of the string is
inserted into the out vector. inserted into the out vector.
We ignore ANY_STRING_RECURSIVE here. The consequence is that you cannot
tab complete ** wildcards. This is historic behavior.
*/ */
static bool wildcard_complete_internal(const wcstring &orig, static bool wildcard_complete_internal(const wchar_t *str,
const wchar_t *str,
const wchar_t *wc, const wchar_t *wc,
bool is_first, const wc_complete_pack_t &params,
const wchar_t *desc, complete_flags_t flags,
wcstring(*desc_func)(const wcstring &),
std::vector<completion_t> *out, std::vector<completion_t> *out,
expand_flags_t expand_flags, bool is_first_call = false)
complete_flags_t flags)
{ {
if (!wc || ! str || orig.empty()) assert(str != NULL);
assert(wc != NULL);
/* Maybe early out for hidden files. We require that the wildcard match these exactly (i.e. a dot); ANY_STRING not allowed */
if (is_first_call && str[0] == L'.' && wc[0] != L'.')
{ {
debug(2, L"Got null string on line %d of file %s", __LINE__, __FILE__); return false;
return 0;
} }
bool has_match = false; /* Locate the next wildcard character position, e.g. ANY_CHAR or ANY_STRING */
string_fuzzy_match_t fuzzy_match(fuzzy_match_exact); size_t next_wc_char_pos = wildcard_find(wc);
const bool at_end_of_wildcard = (*wc == L'\0');
const wchar_t *completion_string = NULL;
// Hack hack hack /* Maybe we have no more wildcards at all. */
// Implement EXPAND_FUZZY_MATCH by short-circuiting everything if there are no remaining wildcards if (next_wc_char_pos == wcstring::npos)
if ((expand_flags & EXPAND_FUZZY_MATCH) && ! at_end_of_wildcard && ! wildcard_has(wc, true))
{ {
string_fuzzy_match_t local_fuzzy_match = string_fuzzy_match_string(wc, str); string_fuzzy_match_t match = string_fuzzy_match_string(wc, str);
if (local_fuzzy_match.type != fuzzy_match_none)
{
has_match = true;
fuzzy_match = local_fuzzy_match;
/* If we're not a prefix or exact match, then we need to replace the token. Note that in this case we're not going to call ourselves recursively, so these modified flags won't "leak" except into the completion. */ /* If we're allowing fuzzy match, any match is OK. Otherwise we require a prefix match. */
if (match_type_requires_full_replacement(local_fuzzy_match.type)) bool match_acceptable;
if (params.expand_flags & EXPAND_FUZZY_MATCH)
{ {
flags |= COMPLETE_REPLACES_TOKEN; match_acceptable = match.type != fuzzy_match_none;
completion_string = orig.c_str();
} }
else else
{ {
/* Since we are not replacing the token, be careful to only store the part of the string after the wildcard */ match_acceptable = match_type_shares_prefix(match.type);
size_t wc_len = wcslen(wc);
assert(wcslen(str) >= wc_len);
completion_string = str + wcslen(wc);
}
}
} }
/* Maybe we satisfied the wildcard normally */ if (match_acceptable)
if (! has_match)
{
bool file_has_leading_dot = (is_first && str[0] == L'.');
if (at_end_of_wildcard && ! file_has_leading_dot)
{
has_match = true;
if (flags & COMPLETE_REPLACES_TOKEN)
{
completion_string = orig.c_str();
}
else
{
completion_string = str;
}
}
}
if (has_match)
{ {
/* Wildcard complete */ /* Wildcard complete */
assert(completion_string != NULL); bool full_replacement = match_type_requires_full_replacement(match.type) || (flags & COMPLETE_REPLACES_TOKEN);
wcstring out_completion = completion_string;
wcstring out_desc = (desc ? desc : L"");
size_t complete_sep_loc = out_completion.find(PROG_COMPLETE_SEP); /* If we are not replacing the token, be careful to only store the part of the string after the wildcard */
if (complete_sep_loc != wcstring::npos) assert(!full_replacement || wcslen(wc) <= wcslen(str));
wcstring out_completion = full_replacement ? params.orig : str + wcslen(wc);
wcstring out_desc = resolve_description(&out_completion, params.desc, params.desc_func);
/* Note: out_completion may be empty if the completion really is empty, e.g. tab-completing 'foo' when a file 'foo' exists. */
complete_flags_t local_flags = flags | (full_replacement ? COMPLETE_REPLACES_TOKEN : 0);
append_completion(out, out_completion, out_desc, local_flags, match);
}
return match_acceptable;
}
else if (next_wc_char_pos > 0)
{ {
/* This completion has an embedded description, do not use the generic description */ bool result;
out_desc.assign(out_completion, complete_sep_loc + 1, out_completion.size() - complete_sep_loc - 1); /* Here we have a non-wildcard prefix. Note that we don't do fuzzy matching for stuff before a wildcard, so just do case comparison and then recurse. */
out_completion.resize(complete_sep_loc); if (wcsncmp(str, wc, next_wc_char_pos) == 0)
{
// Normal match
result = wildcard_complete_internal(str + next_wc_char_pos, wc + next_wc_char_pos, params, flags, out);
}
else if (wcsncasecmp(str, wc, next_wc_char_pos) == 0)
{
// Case insensitive match
result = wildcard_complete_internal(str + next_wc_char_pos, wc + next_wc_char_pos, params, flags | COMPLETE_REPLACES_TOKEN, out);
} }
else else
{ {
if (desc_func && !(expand_flags & EXPAND_NO_DESCRIPTIONS)) // No match
result = false;
}
return result;
}
else
{ {
/* /* Our first character is a wildcard. */
A description generating function is specified, call assert(next_wc_char_pos == 0);
it. If it returns something, use that as the switch (wc[0])
description. {
*/ case ANY_CHAR:
wcstring func_desc = desc_func(orig); {
if (! func_desc.empty()) if (str[0] == L'\0')
out_desc = func_desc;
}
}
/* Note: out_completion may be empty if the completion really is empty, e.g. tab-completing 'foo' when a file 'foo' exists. */
append_completion(out, out_completion, out_desc, flags, fuzzy_match);
return true;
}
if (*wc == ANY_STRING)
{ {
bool res=false;
/* Ignore hidden file */
if (is_first && str[0] == L'.')
return false; return false;
}
else
{
return wildcard_complete_internal(str + 1, wc + 1, params, flags, out);
}
break;
}
/* Try all submatches */ case ANY_STRING:
for (size_t i=0; str[i] != L'\0'; i++) {
bool has_match = false;
/* Try all submatches. #929: if the recursive call gives us a prefix match, just stop. This is sloppy - what we really want to do is say, once we've seen a match of a particular type, ignore all matches of that type further down the string, such that the wildcard produces the "minimal match." */
bool has_prefix_match = false;
for (size_t i=0; str[i] != L'\0' && ! has_prefix_match; i++)
{ {
const size_t before_count = out->size(); const size_t before_count = out->size();
if (wildcard_complete_internal(orig, str + i, wc+1, false, desc, desc_func, out, expand_flags, flags)) if (wildcard_complete_internal(str + i, wc + 1, params, flags, out))
{ {
res = true; has_match = true;
/* #929: if the recursive call gives us a prefix match, just stop. This is sloppy - what we really want to do is say, once we've seen a match of a particular type, ignore all matches of that type further down the string, such that the wildcard produces the "minimal match." */ /* Determine if we have a prefix match */
bool has_prefix_match = false;
const size_t after_count = out->size(); const size_t after_count = out->size();
for (size_t j = before_count; j < after_count; j++) for (size_t j = before_count; j < after_count; j++)
{ {
@ -321,22 +359,21 @@ static bool wildcard_complete_internal(const wcstring &orig,
break; break;
} }
} }
if (has_prefix_match)
break;
} }
} }
return res; return has_match;
}
} case ANY_STRING_RECURSIVE:
else if (*wc == ANY_CHAR || *wc == *str) /* We don't even try with this one */
{
return wildcard_complete_internal(orig, str+1, wc+1, false, desc, desc_func, out, expand_flags, flags);
}
else if (towlower(*wc) == towlower(*str))
{
return wildcard_complete_internal(orig, str+1, wc+1, false, desc, desc_func, out, expand_flags, flags | COMPLETE_REPLACES_TOKEN);
}
return false; return false;
default:
assert(0 && "Unreachable code reached");
return false;
}
}
assert(0 && "Unreachable code reached");
} }
bool wildcard_complete(const wcstring &str, bool wildcard_complete(const wcstring &str,
@ -348,7 +385,11 @@ bool wildcard_complete(const wcstring &str,
complete_flags_t flags) complete_flags_t flags)
{ {
assert(out != NULL); assert(out != NULL);
return wildcard_complete_internal(str, str.c_str(), wc, true, desc, desc_func, out, expand_flags, flags); wc_complete_pack_t params(str);
params.desc = desc;
params.desc_func = desc_func;
params.expand_flags = expand_flags;
return wildcard_complete_internal(str.c_str(), wc, params, flags, out, true /* first call */);
} }