mirror of
https://github.com/fish-shell/fish-shell
synced 2025-01-11 20:48:49 +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,
|
const wcstring &str,
|
||||||
bool use_switches);
|
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,
|
void complete_cmd(const wcstring &str,
|
||||||
bool use_function,
|
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();
|
expand_flags_t flags = EXPAND_SKIP_CMDSUBST | EXPAND_FOR_COMPLETIONS | this->expand_flags();
|
||||||
|
|
||||||
if (! do_file)
|
if (! do_file)
|
||||||
flags |= EXPAND_SKIP_WILDCARDS;
|
flags |= EXPAND_SKIP_WILDCARDS;
|
||||||
|
|
||||||
if (directories_only && do_file)
|
if (handle_as_special_cd && do_file)
|
||||||
flags |= DIRECTORIES_ONLY;
|
{
|
||||||
|
flags |= DIRECTORIES_ONLY | EXPAND_SPECIAL_CD;
|
||||||
|
}
|
||||||
|
|
||||||
/* Squelch file descriptions per issue 254 */
|
/* Squelch file descriptions per issue 254 */
|
||||||
if (this->type() == COMPLETE_AUTOSUGGEST || do_file)
|
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);
|
in_redirection = (redirection != NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool do_file = false, directories_only = false;
|
bool do_file = false, handle_as_special_cd = false;
|
||||||
if (in_redirection)
|
if (in_redirection)
|
||||||
{
|
{
|
||||||
do_file = true;
|
do_file = true;
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
/* Try completing as an argument */
|
||||||
wcstring current_command_unescape, previous_argument_unescape, current_argument_unescape;
|
wcstring current_command_unescape, previous_argument_unescape, current_argument_unescape;
|
||||||
if (unescape_string(current_command, ¤t_command_unescape, UNESCAPE_DEFAULT) &&
|
if (unescape_string(current_command, ¤t_command_unescape, UNESCAPE_DEFAULT) &&
|
||||||
unescape_string(previous_argument, &previous_argument_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())
|
if (completer.empty())
|
||||||
do_file = true;
|
do_file = true;
|
||||||
|
|
||||||
/* Hack. If we're cd, do directories only (#1059) */
|
/* Hack. If we're cd, handle it specially (#1059, others) */
|
||||||
directories_only = (current_command_unescape == L"cd");
|
handle_as_special_cd = (current_command_unescape == L"cd");
|
||||||
|
|
||||||
/* And if we're autosuggesting, and the token is empty, don't do file suggestions */
|
/* And if we're autosuggesting, and the token is empty, don't do file suggestions */
|
||||||
if ((flags & COMPLETION_REQUEST_AUTOSUGGESTION) && current_argument_unescape.empty())
|
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 */
|
/* 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 "env.h"
|
||||||
#include "proc.h"
|
#include "proc.h"
|
||||||
#include "parser.h"
|
#include "parser.h"
|
||||||
|
#include "path.h"
|
||||||
#include "expand.h"
|
#include "expand.h"
|
||||||
#include "wildcard.h"
|
#include "wildcard.h"
|
||||||
#include "exec.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;
|
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)
|
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))) ||
|
else if (((flags & EXPAND_FOR_COMPLETIONS) && (!(flags & EXPAND_SKIP_WILDCARDS))) ||
|
||||||
has_wildcard)
|
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) */
|
/* 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.
|
||||||
wcstring start, rest;
|
|
||||||
|
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"/";
|
/* Common case */
|
||||||
rest = next.substr(1);
|
if (string_prefixes_string(L"/", next))
|
||||||
}
|
|
||||||
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)
|
|
||||||
{
|
{
|
||||||
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;
|
/* Ensure that we use the working directory for relative cdpaths like "." */
|
||||||
break;
|
base_dirs.push_back(path_apply_working_directory(next_path, working_directory));
|
||||||
}
|
|
||||||
|
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
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
|
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))
|
if (!(flags & EXPAND_FOR_COMPLETIONS))
|
||||||
{
|
{
|
||||||
append_completion(out, next);
|
append_completion(out, next);
|
||||||
|
|
|
@ -59,7 +59,10 @@ enum
|
||||||
EXPAND_FUZZY_MATCH = 1 << 9,
|
EXPAND_FUZZY_MATCH = 1 << 9,
|
||||||
|
|
||||||
/** Disallow directory abbreviations like /u/l/b for /usr/local/bin. Only applicable if EXPAND_FUZZY_MATCH is set. */
|
/** 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;
|
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. */
|
/* Determine if the filesystem containing the given fd is case insensitive. */
|
||||||
typedef std::map<wcstring, bool> case_sensitivity_cache_t;
|
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)
|
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 &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 */
|
/* Skip this if it's empty or we've already checked it */
|
||||||
if (abs_path.empty() || checked_paths.count(abs_path))
|
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))
|
while (tokenizer.next(next_path))
|
||||||
{
|
{
|
||||||
/* Ensure that we use the working directory for relative cdpaths like "." */
|
/* 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
|
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. */
|
/* 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)
|
switch (redirect_type)
|
||||||
{
|
{
|
||||||
case TOK_REDIRECT_FD:
|
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;
|
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()
|
static wcstring path_create_config()
|
||||||
{
|
{
|
||||||
bool done = false;
|
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 */
|
/** Returns whether the two paths refer to the same file */
|
||||||
bool paths_are_same_file(const wcstring &path1, const wcstring &path2);
|
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
|
#endif
|
||||||
|
|
Loading…
Reference in a new issue