aboutsummaryrefslogtreecommitdiffhomepage
diff options
context:
space:
mode:
authorGravatar ridiculousfish <corydoras@ridiculousfish.com>2016-02-06 14:39:47 -0800
committerGravatar ridiculousfish <corydoras@ridiculousfish.com>2016-02-18 17:00:25 -0800
commit2d68b250253eb07f8f796a6fe5f421efc90b70e1 (patch)
treeeed41798a1f5d624a4b3366c683569d5065b227b
parent1767681f9a61ac0789f5dafa76126e3446924b6d (diff)
Early work towards moving the cd special autosuggestion into completions
This will simplify some code and make the cd autosuggestion smarter
-rw-r--r--src/complete.cpp23
-rw-r--r--src/expand.cpp119
-rw-r--r--src/expand.h5
-rw-r--r--src/highlight.cpp52
-rw-r--r--src/path.cpp48
-rw-r--r--src/path.h3
6 files changed, 157 insertions, 93 deletions
diff --git a/src/complete.cpp b/src/complete.cpp
index 5aa25305..790971f8 100644
--- a/src/complete.cpp
+++ b/src/complete.cpp
@@ -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);
}
}
}
diff --git a/src/expand.cpp b/src/expand.cpp
index dde7ddac..3be1c679 100644
--- a/src/expand.cpp
+++ b/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);
+ /* Common case */
+ if (string_prefixes_string(L"/", next))
+ {
+ base_dirs.push_back(L"/");
+ path_remainder = next.substr(1);
+ }
+ else
+ {
+ base_dirs.push_back(L"");
+ path_remainder = next;
+ }
}
else
{
- start = L"";
- rest = next;
+ /* 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))
+ {
+ /* 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;
- int wc_res = wildcard_expand_string(rest, start, flags, &expanded);
- if (flags & EXPAND_FOR_COMPLETIONS)
- {
- out->insert(out->end(), expanded.begin(), expanded.end());
- }
- else
+ for (size_t base_dir_idx = 0; base_dir_idx < base_dirs.size(); base_dir_idx++)
{
- switch (wc_res)
+ int local_wc_res = wildcard_expand_string(path_remainder, base_dirs.at(base_dir_idx), flags, &expanded);
+ if (local_wc_res > 0)
{
- case 0:
- {
- 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;
- }
+ // 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);
diff --git a/src/expand.h b/src/expand.h
index e81f7143..2c526358 100644
--- a/src/expand.h
+++ b/src/expand.h
@@ -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;
diff --git a/src/highlight.cpp b/src/highlight.cpp
index 3c70f68c..d2427d7c 100644
--- a/src/highlight.cpp
+++ b/src/highlight.cpp
@@ -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:
diff --git a/src/path.cpp b/src/path.cpp
index b8cfd516..a85b662f 100644
--- a/src/path.cpp
+++ b/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;
diff --git a/src/path.h b/src/path.h
index 973e8a3a..eec776a6 100644
--- a/src/path.h
+++ b/src/path.h
@@ -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