Early work towards moving the cd special autosuggestion into completions

This will simplify some code and make the cd autosuggestion smarter
This commit is contained in:
ridiculousfish 2016-02-06 14:39:47 -08:00
parent 1767681f9a
commit 2d68b25025
6 changed files with 162 additions and 98 deletions

View file

@ -362,7 +362,9 @@ public:
const wcstring &str,
bool use_switches);
void complete_param_expand(const wcstring &str, bool do_file, bool directories_only = false);
void complete_param_expand(const wcstring &str, bool do_file, bool handle_as_special_cd = false);
void complete_special_cd(const wcstring &str);
void complete_cmd(const wcstring &str,
bool use_function,
@ -1335,17 +1337,19 @@ bool completer_t::complete_param(const wcstring &scmd_orig, const wcstring &spop
}
/**
Perform file completion on the specified string
Perform generic (not command-specific) expansions on the specified string
*/
void completer_t::complete_param_expand(const wcstring &str, bool do_file, bool directories_only)
void completer_t::complete_param_expand(const wcstring &str, bool do_file, bool handle_as_special_cd)
{
expand_flags_t flags = EXPAND_SKIP_CMDSUBST | EXPAND_FOR_COMPLETIONS | this->expand_flags();
if (! do_file)
flags |= EXPAND_SKIP_WILDCARDS;
if (directories_only && do_file)
flags |= DIRECTORIES_ONLY;
if (handle_as_special_cd && do_file)
{
flags |= DIRECTORIES_ONLY | EXPAND_SPECIAL_CD;
}
/* Squelch file descriptions per issue 254 */
if (this->type() == COMPLETE_AUTOSUGGEST || do_file)
@ -1767,13 +1771,14 @@ void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> &comps
in_redirection = (redirection != NULL);
}
bool do_file = false, directories_only = false;
bool do_file = false, handle_as_special_cd = false;
if (in_redirection)
{
do_file = true;
}
else
{
/* Try completing as an argument */
wcstring current_command_unescape, previous_argument_unescape, current_argument_unescape;
if (unescape_string(current_command, &current_command_unescape, UNESCAPE_DEFAULT) &&
unescape_string(previous_argument, &previous_argument_unescape, UNESCAPE_DEFAULT) &&
@ -1813,8 +1818,8 @@ void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> &comps
if (completer.empty())
do_file = true;
/* Hack. If we're cd, do directories only (#1059) */
directories_only = (current_command_unescape == L"cd");
/* Hack. If we're cd, handle it specially (#1059, others) */
handle_as_special_cd = (current_command_unescape == L"cd");
/* And if we're autosuggesting, and the token is empty, don't do file suggestions */
if ((flags & COMPLETION_REQUEST_AUTOSUGGESTION) && current_argument_unescape.empty())
@ -1824,7 +1829,7 @@ void complete(const wcstring &cmd_with_subcmds, std::vector<completion_t> &comps
}
/* This function wants the unescaped string */
completer.complete_param_expand(current_token, do_file, directories_only);
completer.complete_param_expand(current_token, do_file, handle_as_special_cd);
}
}
}

View file

@ -39,6 +39,7 @@ parameter expansion.
#include "env.h"
#include "proc.h"
#include "parser.h"
#include "path.h"
#include "expand.h"
#include "wildcard.h"
#include "exec.h"
@ -1776,6 +1777,29 @@ static expand_error_t expand_stage_home_and_pid(const wcstring &input, std::vect
}
return EXPAND_OK;
}
#if 0
if (string_prefixes_string(L"./", path))
{
/* Ignore the CDPATH in this case; just use the working directory */
directories.push_back(working_directory);
}
else
{
/* Get the CDPATH */
env_var_t cdpath = env_get_string(L"CDPATH");
if (cdpath.missing_or_empty())
cdpath = L".";
/* Tokenize it into directories */
wcstokenizer tokenizer(cdpath, ARRAY_SEP_STR);
wcstring next_path;
while (tokenizer.next(next_path))
{
/* Ensure that we use the working directory for relative cdpaths like "." */
directories.push_back(apply_working_directory(next_path, working_directory));
}
}
#endif
static expand_error_t expand_stage_wildcards(const wcstring &input, std::vector<completion_t> *out, expand_flags_t flags, parse_error_list_t *errors)
{
@ -1792,55 +1816,82 @@ static expand_error_t expand_stage_wildcards(const wcstring &input, std::vector<
else if (((flags & EXPAND_FOR_COMPLETIONS) && (!(flags & EXPAND_SKIP_WILDCARDS))) ||
has_wildcard)
{
/* We either have a wildcard, or we don't have a wildcard but we're doing completion expansion (so we want to get the completion of a file path) */
wcstring start, rest;
/* We either have a wildcard, or we don't have a wildcard but we're doing completion expansion (so we want to get the completion of a file path). Note that if EXPAND_SKIP_WILDCARDS is set, we stomped wildcards in remove_internal_separator above, so there actually aren't any.
So we're going to treat this input as a file path. Compute the base path. This may be literal if we start with / or ./, otherwise it may be CDPATH if the special flag is set.
*/
if (next[0] == L'/')
wcstring_list_t base_dirs;
wcstring path_remainder;
if (! (flags & EXPAND_SPECIAL_CD))
{
start = L"/";
rest = next.substr(1);
}
else
{
start = L"";
rest = next;
}
std::vector<completion_t> expanded;
int wc_res = wildcard_expand_string(rest, start, flags, &expanded);
if (flags & EXPAND_FOR_COMPLETIONS)
{
out->insert(out->end(), expanded.begin(), expanded.end());
}
else
{
switch (wc_res)
/* Common case */
if (string_prefixes_string(L"/", next))
{
case 0:
base_dirs.push_back(L"/");
path_remainder = next.substr(1);
}
else
{
base_dirs.push_back(L"");
path_remainder = next;
}
}
else
{
/* Ignore the CDPATH if we start with ./ or / */
if (string_prefixes_string(L"./", next))
{
base_dirs.push_back(L"");
path_remainder = next;
}
else if (string_prefixes_string(L"/", next))
{
base_dirs.push_back(L"/");
path_remainder = next.substr(1);
}
else
{
/* Get the CDPATH and cwd. Perhaps these should be passed in. */
const wcstring working_directory = env_get_pwd_slash();
env_var_t cdpath = env_get_string(L"CDPATH");
if (cdpath.missing_or_empty())
cdpath = L".";
/* Tokenize it into directories */
wcstokenizer tokenizer(cdpath, ARRAY_SEP_STR);
wcstring next_path;
while (tokenizer.next(next_path))
{
result = EXPAND_WILDCARD_NO_MATCH;
break;
}
case 1:
{
result = EXPAND_WILDCARD_MATCH;
std::sort(expanded.begin(), expanded.end(), completion_t::is_naturally_less_than);
out->insert(out->end(), expanded.begin(), expanded.end());
break;
}
case -1:
{
result = EXPAND_ERROR;
break;
/* Ensure that we use the working directory for relative cdpaths like "." */
base_dirs.push_back(path_apply_working_directory(next_path, working_directory));
}
}
}
result = EXPAND_WILDCARD_NO_MATCH;
std::vector<completion_t> expanded;
for (size_t base_dir_idx = 0; base_dir_idx < base_dirs.size(); base_dir_idx++)
{
int local_wc_res = wildcard_expand_string(path_remainder, base_dirs.at(base_dir_idx), flags, &expanded);
if (local_wc_res > 0)
{
// Something matched,so overall we matched
result = EXPAND_WILDCARD_MATCH;
}
else if (local_wc_res < 0)
{
// Cancellation
result = EXPAND_ERROR;
break;
}
}
out->insert(out->end(), expanded.begin(), expanded.end());
}
else
{
/* Can't yet justify this check */
/* Can't fully justify this check. I think it's that SKIP_WILDCARDS is used when completing to mean don't do file expansions, so if we're not doing file expansions, just drop this completion on the floor. */
if (!(flags & EXPAND_FOR_COMPLETIONS))
{
append_completion(out, next);

View file

@ -59,7 +59,10 @@ enum
EXPAND_FUZZY_MATCH = 1 << 9,
/** Disallow directory abbreviations like /u/l/b for /usr/local/bin. Only applicable if EXPAND_FUZZY_MATCH is set. */
EXPAND_NO_FUZZY_DIRECTORIES = 1 << 10
EXPAND_NO_FUZZY_DIRECTORIES = 1 << 10,
/** Do expansions specifically to support cd (CDPATH, etc) */
EXPAND_SPECIAL_CD = 1 << 11
};
typedef int expand_flags_t;

View file

@ -65,52 +65,6 @@ static const wchar_t * const highlight_var[] =
};
/* If the given path looks like it's relative to the working directory, then prepend that working directory. This operates on unescaped paths only (so a ~ means a literal ~) */
static wcstring apply_working_directory(const wcstring &path, const wcstring &working_directory)
{
if (path.empty() || working_directory.empty())
return path;
/* We're going to make sure that if we want to prepend the wd, that the string has no leading / */
bool prepend_wd;
switch (path.at(0))
{
case L'/':
case HOME_DIRECTORY:
prepend_wd = false;
break;
default:
prepend_wd = true;
break;
}
if (! prepend_wd)
{
/* No need to prepend the wd, so just return the path we were given */
return path;
}
else
{
/* Remove up to one ./ */
wcstring path_component = path;
if (string_prefixes_string(L"./", path_component))
{
path_component.erase(0, 2);
}
/* Removing leading /s */
while (string_prefixes_string(L"/", path_component))
{
path_component.erase(0, 1);
}
/* Construct and return a new path */
wcstring new_path = working_directory;
append_path_component(new_path, path_component);
return new_path;
}
}
/* Determine if the filesystem containing the given fd is case insensitive. */
typedef std::map<wcstring, bool> case_sensitivity_cache_t;
bool fs_is_case_insensitive(const wcstring &path, int fd, case_sensitivity_cache_t &case_sensitivity_cache)
@ -261,7 +215,7 @@ bool is_potential_path(const wcstring &potential_path_fragment, const wcstring_l
{
const wcstring &wd = directories.at(wd_idx);
const wcstring abs_path = apply_working_directory(clean_potential_path_fragment, wd);
const wcstring abs_path = path_apply_working_directory(clean_potential_path_fragment, wd);
/* Skip this if it's empty or we've already checked it */
if (abs_path.empty() || checked_paths.count(abs_path))
@ -399,7 +353,7 @@ static bool is_potential_cd_path(const wcstring &path, const wcstring &working_d
while (tokenizer.next(next_path))
{
/* Ensure that we use the working directory for relative cdpaths like "." */
directories.push_back(apply_working_directory(next_path, working_directory));
directories.push_back(path_apply_working_directory(next_path, working_directory));
}
}
@ -1226,7 +1180,7 @@ void highlighter_t::color_redirection(const parse_node_t &redirection_node)
else
{
/* Ok, we successfully expanded our target. Now verify that it works with this redirection. We will probably need it as a path (but not in the case of fd redirections). Note that the target is now unescaped. */
const wcstring target_path = apply_working_directory(target, this->working_directory);
const wcstring target_path = path_apply_working_directory(target, this->working_directory);
switch (redirect_type)
{
case TOK_REDIRECT_FD:

View file

@ -240,6 +240,54 @@ bool path_can_be_implicit_cd(const wcstring &path, wcstring *out_path, const wch
return result;
}
/* If the given path looks like it's relative to the working directory, then prepend that working directory. This operates on unescaped paths only (so a ~ means a literal ~) */
wcstring path_apply_working_directory(const wcstring &path, const wcstring &working_directory)
{
if (path.empty() || working_directory.empty())
return path;
/* We're going to make sure that if we want to prepend the wd, that the string has no leading / */
bool prepend_wd;
switch (path.at(0))
{
case L'/':
case HOME_DIRECTORY:
prepend_wd = false;
break;
default:
prepend_wd = true;
break;
}
if (! prepend_wd)
{
/* No need to prepend the wd, so just return the path we were given */
return path;
}
else
{
/* Remove up to one ./ */
wcstring path_component = path;
if (string_prefixes_string(L"./", path_component))
{
path_component.erase(0, 2);
}
/* Removing leading /s */
while (string_prefixes_string(L"/", path_component))
{
path_component.erase(0, 1);
}
/* Construct and return a new path */
wcstring new_path = working_directory;
append_path_component(new_path, path_component);
return new_path;
}
}
static wcstring path_create_config()
{
bool done = false;

View file

@ -96,4 +96,7 @@ bool path_is_valid(const wcstring &path, const wcstring &working_directory);
/** Returns whether the two paths refer to the same file */
bool paths_are_same_file(const wcstring &path1, const wcstring &path2);
/* If the given path looks like it's relative to the working directory, then prepend that working directory. This operates on unescaped paths only (so a ~ means a literal ~) */
wcstring path_apply_working_directory(const wcstring &path, const wcstring &working_directory);
#endif