mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-23 19:33:12 +00:00
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:
parent
1767681f9a
commit
2d68b25025
6 changed files with 162 additions and 98 deletions
|
@ -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, ¤t_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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
129
src/expand.cpp
129
src/expand.cpp
|
@ -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);
|
||||
|
|
|
@ -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;
|
||||
|
||||
|
|
|
@ -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:
|
||||
|
|
48
src/path.cpp
48
src/path.cpp
|
@ -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;
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue