mirror of
https://github.com/fish-shell/fish-shell
synced 2024-12-26 12:53:13 +00:00
Factor is_potential_path to properly handle CDPATH
This will let us color cd commands better
This commit is contained in:
parent
1a264ab7c2
commit
0c79bb6e7c
5 changed files with 211 additions and 237 deletions
300
highlight.cpp
300
highlight.cpp
|
@ -64,22 +64,54 @@ static const wchar_t * const highlight_var[] =
|
|||
L"fish_color_autosuggestion"
|
||||
};
|
||||
|
||||
/**
|
||||
Tests if the specified string is the prefix of any valid path in the system.
|
||||
/* If the given path looks like it's relative to the working directory, then prepend that working directory. */
|
||||
static wcstring apply_working_directory(const wcstring &path, const wcstring &working_directory) {
|
||||
if (path.empty() || working_directory.empty())
|
||||
return path;
|
||||
|
||||
\require_dir Whether the valid path must be a directory
|
||||
\out_path If non-null, the path on output
|
||||
\return zero it this is not a valid prefix, non-zero otherwise
|
||||
*/
|
||||
// PCA DOES_IO
|
||||
static bool is_potential_path( const wcstring &cpath, wcstring *out_path = NULL, bool require_dir = false )
|
||||
/* 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 L'~':
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
/* Tests whether the specified string cpath is the prefix of anything we could cd to. directories is a list of possible parent directories (typically either the working directory, or the cdpath). This does I/O! */
|
||||
static bool is_potential_path( const wcstring &cpath, const wcstring_list_t &directories, bool require_dir = false, wcstring *out_path = NULL)
|
||||
{
|
||||
ASSERT_IS_BACKGROUND_THREAD();
|
||||
|
||||
const wchar_t *unescaped, *in;
|
||||
wcstring cleaned_path;
|
||||
wcstring clean_path;
|
||||
int has_magic = 0;
|
||||
bool res = false;
|
||||
bool result = false;
|
||||
|
||||
wcstring path(cpath);
|
||||
expand_tilde(path);
|
||||
|
@ -115,7 +147,7 @@ static bool is_potential_path( const wcstring &cpath, wcstring *out_path = NULL,
|
|||
|
||||
default:
|
||||
{
|
||||
cleaned_path += *in;
|
||||
clean_path.append(in, 1);
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -123,41 +155,58 @@ static bool is_potential_path( const wcstring &cpath, wcstring *out_path = NULL,
|
|||
|
||||
}
|
||||
|
||||
if( !has_magic && ! cleaned_path.empty() )
|
||||
if( ! has_magic && ! clean_path.empty() )
|
||||
{
|
||||
bool must_be_full_dir = cleaned_path[cleaned_path.length()-1] == L'/';
|
||||
DIR *dir;
|
||||
/* Don't test the same path multiple times, which can happen if the path is absolute and the CDPATH contains multiple entries */
|
||||
std::set<wcstring> checked_paths;
|
||||
|
||||
for (size_t wd_idx = 0; wd_idx < directories.size() && ! result; wd_idx++) {
|
||||
const wcstring &wd = directories.at(wd_idx);
|
||||
|
||||
const wcstring abs_path = apply_working_directory(clean_path, wd);
|
||||
|
||||
/* Skip this if it's empty or we've already checked it */
|
||||
if (abs_path.empty() || checked_paths.count(abs_path))
|
||||
continue;
|
||||
checked_paths.insert(abs_path);
|
||||
|
||||
/* If we end with a slash, then it must be a directory */
|
||||
bool must_be_full_dir = abs_path.at(abs_path.size()-1) == L'/';
|
||||
if (must_be_full_dir)
|
||||
{
|
||||
dir = wopendir( cleaned_path );
|
||||
if( dir )
|
||||
{
|
||||
res = true;
|
||||
struct stat buf;
|
||||
if (0 == wstat(abs_path, &buf) && S_ISDIR(buf.st_mode)) {
|
||||
result = true;
|
||||
/* Return the path suffix, not the whole absolute path */
|
||||
if (out_path)
|
||||
*out_path = cleaned_path;
|
||||
closedir( dir );
|
||||
*out_path = clean_path;
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
wcstring dir_name = wdirname(cleaned_path);
|
||||
wcstring base_name = wbasename(cleaned_path);
|
||||
DIR *dir = NULL;
|
||||
|
||||
/* We do not end with a slash; it does not have to be a directory */
|
||||
const wcstring dir_name = wdirname(abs_path);
|
||||
const wcstring base_name = wbasename(abs_path);
|
||||
if (dir_name == L"/" && base_name == L"/")
|
||||
{
|
||||
res = true;
|
||||
result = true;
|
||||
if (out_path)
|
||||
*out_path = cleaned_path;
|
||||
*out_path = clean_path;
|
||||
}
|
||||
else if( (dir = wopendir( dir_name)) )
|
||||
{
|
||||
else if ((dir = wopendir(dir_name))) {
|
||||
/* We opened the dir_name; look for a string where the base name prefixes it */
|
||||
wcstring ent;
|
||||
bool is_dir;
|
||||
while (wreaddir_resolving(dir, dir_name, ent, &is_dir))
|
||||
|
||||
// Don't ask for the is_dir value unless we care, because it can cause extra filesystem acces */
|
||||
bool is_dir = false;
|
||||
while (wreaddir_resolving(dir, dir_name, ent, require_dir ? &is_dir : NULL))
|
||||
{
|
||||
/* TODO: support doing the right thing on case-insensitive filesystems like HFS+ */
|
||||
if (string_prefixes_string(base_name, ent) && (! require_dir || is_dir))
|
||||
{
|
||||
res = true;
|
||||
result = true;
|
||||
if (out_path) {
|
||||
out_path->assign(dir_name);
|
||||
out_path->push_back(L'/');
|
||||
|
@ -170,15 +219,34 @@ static bool is_potential_path( const wcstring &cpath, wcstring *out_path = NULL,
|
|||
break;
|
||||
}
|
||||
}
|
||||
|
||||
closedir(dir);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
return res;
|
||||
|
||||
/* Given a string, return whether it prefixes a path that we could cd into. Return that path in out_path */
|
||||
static bool is_potential_cd_path(const wcstring &path, const wcstring &working_directory, wcstring *out_path) {
|
||||
/* Get the CDPATH */
|
||||
env_var_t cdpath = env_get_string(L"CDPATH");
|
||||
if (cdpath.missing_or_empty())
|
||||
cdpath = L".";
|
||||
|
||||
/* Tokenize it into directories */
|
||||
wcstring_list_t 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));
|
||||
}
|
||||
|
||||
/* Call is_potential_path */
|
||||
return is_potential_path(path, directories, true /* require_dir */, out_path);
|
||||
}
|
||||
|
||||
rgb_color_t highlight_get_color( int highlight, bool is_background )
|
||||
|
@ -235,7 +303,6 @@ rgb_color_t highlight_get_color( int highlight, bool is_background )
|
|||
*/
|
||||
static void highlight_param( const wcstring &buffstr, std::vector<int> &colors, int pos, wcstring_list_t *error )
|
||||
{
|
||||
return;
|
||||
const wchar_t * const buff = buffstr.c_str();
|
||||
enum {e_unquoted, e_single_quoted, e_double_quoted} mode = e_unquoted;
|
||||
size_t in_pos, len = buffstr.size();
|
||||
|
@ -543,13 +610,18 @@ class autosuggest_parsed_command_t {
|
|||
/* Arguments to the command */
|
||||
wcstring_list_t arguments;
|
||||
|
||||
/* Position in the string of the start of the last argument */
|
||||
int last_arg_pos;
|
||||
|
||||
autosuggest_parsed_command_t(const wcstring &str) {
|
||||
if (str.empty())
|
||||
return;
|
||||
|
||||
wcstring cmd;
|
||||
wcstring_list_t args;
|
||||
bool had_cmd = false, recognized_cmd = false;
|
||||
int arg_pos = -1;
|
||||
|
||||
bool had_cmd = false;
|
||||
tokenizer tok;
|
||||
for (tok_init( &tok, str.c_str(), TOK_SQUASH_ERRORS); tok_has_next(&tok); tok_next(&tok))
|
||||
{
|
||||
|
@ -563,6 +635,7 @@ class autosuggest_parsed_command_t {
|
|||
{
|
||||
/* Parameter to the command */
|
||||
args.push_back(tok_last(&tok));
|
||||
arg_pos = tok_get_pos(&tok);
|
||||
}
|
||||
else
|
||||
{
|
||||
|
@ -631,6 +704,7 @@ class autosuggest_parsed_command_t {
|
|||
had_cmd = false;
|
||||
cmd.empty();
|
||||
args.empty();
|
||||
arg_pos = -1;
|
||||
break;
|
||||
}
|
||||
|
||||
|
@ -648,41 +722,32 @@ class autosuggest_parsed_command_t {
|
|||
if (had_cmd) {
|
||||
this->command.swap(cmd);
|
||||
this->arguments.swap(args);
|
||||
this->last_arg_pos = arg_pos;
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
/* Attempts to suggest a completion for a command we handle specially, like 'cd'. Returns true if we recognized the command (even if we couldn't think of a suggestion for it) */
|
||||
bool autosuggest_suggest_special(const wcstring &str, const wcstring &working_directory, wcstring &outString) {
|
||||
bool autosuggest_suggest_special(const wcstring &str, const wcstring &working_directory, wcstring &outSuggestion) {
|
||||
if (str.empty())
|
||||
return false;
|
||||
|
||||
wcstring cmd;
|
||||
bool had_cmd = false, recognized_cmd = false;
|
||||
ASSERT_IS_BACKGROUND_THREAD();
|
||||
|
||||
wcstring suggestion;
|
||||
|
||||
tokenizer tok;
|
||||
for( tok_init( &tok, str.c_str(), TOK_SQUASH_ERRORS );
|
||||
tok_has_next( &tok );
|
||||
tok_next( &tok ) )
|
||||
{
|
||||
int last_type = tok_last_type( &tok );
|
||||
/* Parse the string */
|
||||
const autosuggest_parsed_command_t parsed(str);
|
||||
|
||||
switch( last_type )
|
||||
{
|
||||
case TOK_STRING:
|
||||
{
|
||||
if( had_cmd )
|
||||
{
|
||||
recognized_cmd = (cmd == L"cd");
|
||||
if( recognized_cmd )
|
||||
{
|
||||
wcstring dir = tok_last( &tok );
|
||||
bool result = false;
|
||||
if (parsed.command == L"cd" && ! parsed.arguments.empty()) {
|
||||
/* We can possibly handle this specially */
|
||||
wcstring dir = parsed.arguments.back();
|
||||
wcstring suggested_path;
|
||||
|
||||
if (is_potential_path(dir, &suggested_path, true /* require directory */)) {
|
||||
/* We always return true because we recognized the command. This prevents us from falling back to dumber algorithms; for example we won't suggest a non-directory for the cd command. */
|
||||
result = true;
|
||||
outSuggestion.clear();
|
||||
|
||||
if (is_potential_cd_path(dir, working_directory, &suggested_path)) {
|
||||
|
||||
/* suggested_path needs to actually have dir as a prefix (perhaps with different case). Handle stuff like ./ */
|
||||
bool wants_dot_slash = string_prefixes_string(L"./", dir);
|
||||
|
@ -713,117 +778,15 @@ bool autosuggest_suggest_special(const wcstring &str, const wcstring &working_di
|
|||
}
|
||||
}
|
||||
|
||||
suggestion = str;
|
||||
suggestion.erase(tok_get_pos(&tok));
|
||||
suggestion.append(suggested_path);
|
||||
/* Success */
|
||||
outSuggestion = str;
|
||||
outSuggestion.erase(parsed.last_arg_pos);
|
||||
outSuggestion.append(suggested_path);
|
||||
}
|
||||
} else {
|
||||
/* Either an error or some other command, so we don't handle it specially */
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
/*
|
||||
Command. First check that the command actually exists.
|
||||
*/
|
||||
cmd = tok_last( &tok );
|
||||
bool expanded = expand_one(cmd, EXPAND_SKIP_CMDSUBST | EXPAND_SKIP_VARIABLES);
|
||||
if (! expanded || has_expand_reserved(cmd.c_str()))
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
int is_subcommand = 0;
|
||||
int mark = tok_get_pos( &tok );
|
||||
|
||||
if( parser_keywords_is_subcommand( cmd ) )
|
||||
{
|
||||
|
||||
int sw;
|
||||
|
||||
if( cmd == L"builtin")
|
||||
{
|
||||
}
|
||||
else if( cmd == L"command")
|
||||
{
|
||||
}
|
||||
|
||||
tok_next( &tok );
|
||||
|
||||
sw = parser_keywords_is_switch( tok_last( &tok ) );
|
||||
|
||||
if( !parser_keywords_is_block( cmd ) &&
|
||||
sw == ARG_SWITCH )
|
||||
{
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
if( sw == ARG_SKIP )
|
||||
{
|
||||
mark = tok_get_pos( &tok );
|
||||
}
|
||||
|
||||
is_subcommand = 1;
|
||||
}
|
||||
tok_set_pos( &tok, mark );
|
||||
}
|
||||
|
||||
if( !is_subcommand )
|
||||
{
|
||||
had_cmd = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case TOK_REDIRECT_NOCLOB:
|
||||
case TOK_REDIRECT_OUT:
|
||||
case TOK_REDIRECT_IN:
|
||||
case TOK_REDIRECT_APPEND:
|
||||
case TOK_REDIRECT_FD:
|
||||
{
|
||||
if( !had_cmd )
|
||||
{
|
||||
break;
|
||||
}
|
||||
tok_next( &tok );
|
||||
break;
|
||||
}
|
||||
|
||||
case TOK_PIPE:
|
||||
case TOK_BACKGROUND:
|
||||
{
|
||||
had_cmd = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case TOK_END:
|
||||
{
|
||||
had_cmd = false;
|
||||
break;
|
||||
}
|
||||
|
||||
case TOK_COMMENT:
|
||||
{
|
||||
break;
|
||||
}
|
||||
|
||||
case TOK_ERROR:
|
||||
default:
|
||||
{
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
tok_destroy( &tok );
|
||||
|
||||
if (recognized_cmd) {
|
||||
outString.swap(suggestion);
|
||||
}
|
||||
|
||||
return recognized_cmd;
|
||||
return result;
|
||||
}
|
||||
|
||||
bool autosuggest_special_validate_from_history(const wcstring &str, const wcstring &working_directory, bool *outSuggestionOK) {
|
||||
|
@ -1308,6 +1271,9 @@ void highlight_shell( const wcstring &buff, std::vector<int> &color, int pos, wc
|
|||
color.at(i) = last_val;
|
||||
}
|
||||
|
||||
/* Do something sucky and get the current working directory on this background thread. This should really be passed in. Note this needs to be a vector (of one directory). */
|
||||
const wcstring_list_t working_directories(1, get_working_directory());
|
||||
|
||||
/*
|
||||
Color potentially valid paths in a special path color if they
|
||||
are the current token.
|
||||
|
@ -1322,7 +1288,7 @@ void highlight_shell( const wcstring &buff, std::vector<int> &color, int pos, wc
|
|||
if( tok_begin && tok_end )
|
||||
{
|
||||
const wcstring token(tok_begin, tok_end-tok_begin);
|
||||
if( is_potential_path( token ) )
|
||||
if (is_potential_path(token, working_directories))
|
||||
{
|
||||
for( ptrdiff_t i=tok_begin-cbuff; i < (tok_end-cbuff); i++ )
|
||||
{
|
||||
|
|
|
@ -106,7 +106,13 @@ void highlight_universal( const wcstring &buffstr, std::vector<int> &color, int
|
|||
*/
|
||||
rgb_color_t highlight_get_color( int highlight, bool is_background );
|
||||
|
||||
/** Given a command 'str' from the history, try to determine whether we ought to suggest it by specially recognizing the command.
|
||||
Returns true if we validated the command. If so, returns by reference whether the suggestion is valid or not.
|
||||
*/
|
||||
bool autosuggest_special_validate_from_history(const wcstring &str, const wcstring &working_directory, bool *outSuggestionOK);
|
||||
|
||||
/** Given the command line contents 'str', return via reference a suggestion by specially recognizing the command. Returns true if we recognized the command (even if we couldn't think of a suggestion for it).
|
||||
*/
|
||||
bool autosuggest_suggest_special(const wcstring &str, const wcstring &working_directory, wcstring &outString);
|
||||
|
||||
|
||||
|
|
1
path.h
1
path.h
|
@ -68,6 +68,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);
|
||||
|
||||
/* Returns the current working directory as returned by wgetcwd */
|
||||
wcstring get_working_directory(void);
|
||||
|
||||
#endif
|
||||
|
|
|
@ -1296,8 +1296,8 @@ struct autosuggestion_context_t {
|
|||
|
||||
// Here we do something a little funny
|
||||
// If the line ends with a space, and the cursor is not at the end,
|
||||
// Don't use completion autosuggestions. It ends up being pretty weird seeing stuff get spammed on the right
|
||||
// While you go back to edit a line
|
||||
// don't use completion autosuggestions. It ends up being pretty weird seeing stuff get spammed on the right
|
||||
// while you go back to edit a line
|
||||
const bool line_ends_with_space = iswspace(search_string.at(search_string.size() - 1));
|
||||
const bool cursor_at_end = (this->cursor_pos == search_string.size());
|
||||
if (line_ends_with_space && ! cursor_at_end)
|
||||
|
|
|
@ -73,10 +73,11 @@ bool wreaddir_resolving(DIR *dir, const std::wstring &dir_path, std::wstring &ou
|
|||
|
||||
out_name = str2wcstring(d->d_name);
|
||||
if (out_is_dir) {
|
||||
/* The caller cares if this is a directory, so check */
|
||||
bool is_dir;
|
||||
if (d->d_type == DT_DIR) {
|
||||
is_dir = true;
|
||||
} else if (d->d_type == DT_LNK) {
|
||||
} else if (d->d_type == DT_LNK || d->d_type == DT_UNKNOWN) {
|
||||
/* We want to treat symlinks to directories as directories. Use stat to resolve it. */
|
||||
cstring fullpath = wcs2string(dir_path);
|
||||
fullpath.push_back('/');
|
||||
|
|
Loading…
Reference in a new issue