Changes to make autosuggestion even smarter by specially recognizing the cd command.

This commit is contained in:
ridiculousfish 2012-02-18 18:54:36 -08:00
parent ed89df7e9d
commit 14b3a5be56
8 changed files with 296 additions and 131 deletions

View file

@ -225,7 +225,7 @@
/* Begin PBXLegacyTarget section */ /* Begin PBXLegacyTarget section */
D0A084F713B3AC130099B651 /* FishsFish */ = { D0A084F713B3AC130099B651 /* FishsFish */ = {
isa = PBXLegacyTarget; isa = PBXLegacyTarget;
buildArgumentsString = "-k ${ACTION}"; buildArgumentsString = "-k ${ACTION} -j 3";
buildConfigurationList = D0A084FA13B3AC130099B651 /* Build configuration list for PBXLegacyTarget "FishsFish" */; buildConfigurationList = D0A084FA13B3AC130099B651 /* Build configuration list for PBXLegacyTarget "FishsFish" */;
buildPhases = ( buildPhases = (
); );

View file

@ -1826,12 +1826,14 @@ int create_directory( const wcstring &d )
return ok?0:-1; return ok?0:-1;
} }
__attribute__((noinline))
void bugreport() void bugreport()
{ {
debug( 1, debug( 1,
_( L"This is a bug. " _( L"This is a bug. Break on bugreport to debug."
L"If you can reproduce it, please send a bug report to %s." ), L"If you can reproduce it, please send a bug report to %s." ),
PACKAGE_BUGREPORT ); PACKAGE_BUGREPORT );
while (1) sleep(10000);
} }

View file

@ -528,7 +528,150 @@ static int has_expand_reserved( const wchar_t *str )
return 0; return 0;
} }
bool autosuggest_handle_special(const wcstring &str, const env_vars &vars, const wcstring &working_directory, bool *outSuggestionOK) {
ASSERT_IS_BACKGROUND_THREAD();
assert(outSuggestionOK != NULL);
if (str.empty())
return false;
wcstring cmd;
bool had_cmd = false;
bool handled = false;
bool suggestionOK = true;
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 );
switch( last_type )
{
case TOK_STRING:
{
if( had_cmd )
{
if( cmd == L"cd" )
{
wcstring dir = tok_last( &tok );
if (expand_one(dir, EXPAND_SKIP_CMDSUBST))
{
/* We can specially handle the cd command */
handled = true;
bool is_help = string_prefixes_string(dir, L"--help") || string_prefixes_string(dir, L"-h");
if (! is_help && ! path_can_get_cdpath(dir, working_directory.c_str())) {
suggestionOK = false;
break;
}
}
}
}
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 );
*outSuggestionOK = suggestionOK;
return handled;
}
// This function does I/O // This function does I/O
static void tokenize( const wchar_t * const buff, int * const color, const int pos, wcstring_list_t *error, const env_vars &vars) { static void tokenize( const wchar_t * const buff, int * const color, const int pos, wcstring_list_t *error, const env_vars &vars) {
@ -563,7 +706,6 @@ static void tokenize( const wchar_t * const buff, int * const color, const int p
tok_next( &tok ) ) tok_next( &tok ) )
{ {
int last_type = tok_last_type( &tok ); int last_type = tok_last_type( &tok );
int prev_argc=0;
switch( last_type ) switch( last_type )
{ {
@ -620,8 +762,6 @@ static void tokenize( const wchar_t * const buff, int * const color, const int p
} }
else else
{ {
prev_argc=0;
/* /*
Command. First check that the command actually exists. Command. First check that the command actually exists.
*/ */

View file

@ -106,4 +106,7 @@ void highlight_universal( const wchar_t *buff, int *color, int pos, wcstring_lis
*/ */
rgb_color_t highlight_get_color( int highlight, bool is_background ); rgb_color_t highlight_get_color( int highlight, bool is_background );
bool autosuggest_handle_special(const wcstring &str, const env_vars &vars, const wcstring &working_directory, bool *outSuggestionOK);
#endif #endif

View file

@ -695,26 +695,7 @@ int file_detection_context_t::perform_file_detection(bool test_all) {
valid_paths.clear(); valid_paths.clear();
int result = 1; int result = 1;
for (path_list_t::const_iterator iter = potential_paths.begin(); iter != potential_paths.end(); iter++) { for (path_list_t::const_iterator iter = potential_paths.begin(); iter != potential_paths.end(); iter++) {
wcstring path = *iter; if (path_is_valid(*iter, working_directory)) {
bool path_is_valid;
/* Some special paths are always valid */
if (path.empty()) {
path_is_valid = false;
} else if (path == L"." || path == L"./") {
path_is_valid = true;
} else if (path == L".." || path == L"../") {
path_is_valid = (! working_directory.empty() && working_directory != L"/");
} else {
/* Maybe append the working directory. Note that we know path is not empty here. */
if (path.at(0) != '/') {
path.insert(0, working_directory);
}
path_is_valid = (0 == waccess(path, F_OK));
}
if (path_is_valid) {
/* Push the original (possibly relative) path */ /* Push the original (possibly relative) path */
valid_paths.push_front(*iter); valid_paths.push_front(*iter);
} else { } else {
@ -736,17 +717,9 @@ bool file_detection_context_t::paths_are_valid(const path_list_t &paths) {
file_detection_context_t::file_detection_context_t(history_t *hist, const wcstring &cmd) : file_detection_context_t::file_detection_context_t(history_t *hist, const wcstring &cmd) :
history(hist), history(hist),
command(cmd), command(cmd),
when(time(NULL)) { when(time(NULL)),
/* Stash the working directory. TODO: We should be respecting CDPATH here*/ working_directory(get_working_directory())
wchar_t dir_path[4096]; {
const wchar_t *cwd = wgetcwd( dir_path, 4096 );
if (cwd) {
wcstring wd = cwd;
/* Make sure the working directory ends with a slash */
if (! wd.empty() && wd.at(wd.size() - 1) != L'/')
wd.push_back(L'/');
working_directory.swap(wd);
}
} }
static int threaded_perform_file_detection(file_detection_context_t *ctx) { static int threaded_perform_file_detection(file_detection_context_t *ctx) {

109
path.cpp
View file

@ -336,47 +336,46 @@ bool path_get_cdpath_string(const wcstring &dir_str, wcstring &result, const env
} }
wchar_t *path_allocate_cdpath( const wchar_t *dir ) wchar_t *path_allocate_cdpath( const wchar_t *dir, const wchar_t *wd )
{ {
wchar_t *res = 0; wchar_t *res = NULL;
int err = ENOENT; int err = ENOENT;
if( !dir ) if( !dir )
return 0; return 0;
if (wd) {
if( dir[0] == L'/'|| (wcsncmp( dir, L"./", 2 )==0) ) size_t len = wcslen(wd);
{ assert(wd[len - 1] == L'/');
struct stat buf;
if( wstat( dir, &buf ) == 0 )
{
if( S_ISDIR(buf.st_mode) )
{
res = wcsdup(dir);
}
else
{
err = ENOTDIR;
} }
} wcstring_list_t paths;
}
else if (dir[0] == L'/') {
{ /* Absolute path */
paths.push_back(dir);
} else if (wcsncmp(dir, L"./", 2) == 0 ||
wcsncmp(dir, L"../", 3) == 0 ||
wcscmp(dir, L".") == 0 ||
wcscmp(dir, L"..") == 0) {
/* Path is relative to the working directory */
wcstring path;
if (wd)
path.append(wd);
path.append(dir);
paths.push_back(path);
} else {
wchar_t *path_cpy; wchar_t *path_cpy;
const wchar_t *nxt_path; const wchar_t *nxt_path;
wchar_t *state; wchar_t *state;
wchar_t *whole_path; wchar_t *whole_path;
env_var_t path = L"."; env_var_t path = env_get_string(L"CDPATH");
if (path.missing())
path = wd ? wd : L".";
nxt_path = path.c_str(); nxt_path = path.c_str();
path_cpy = wcsdup( path.c_str() ); path_cpy = wcsdup( path.c_str() );
if( !path_cpy )
{
DIE_MEM();
}
for( nxt_path = wcstok( path_cpy, ARRAY_SEP_STR, &state ); for( nxt_path = wcstok( path_cpy, ARRAY_SEP_STR, &state );
nxt_path != 0; nxt_path != 0;
nxt_path = wcstok( 0, ARRAY_SEP_STR, &state) ) nxt_path = wcstok( 0, ARRAY_SEP_STR, &state) )
@ -398,13 +397,20 @@ wchar_t *path_allocate_cdpath( const wchar_t *dir )
dir ); dir );
free(expanded_path ); free(expanded_path );
paths.push_back(whole_path);
free( whole_path );
}
free( path_cpy );
}
for (wcstring_list_t::const_iterator iter = paths.begin(); iter != paths.end(); iter++) {
struct stat buf; struct stat buf;
if( wstat( whole_path, &buf ) == 0 ) const wchar_t *dir = iter->c_str();
if( wstat( dir, &buf ) == 0 )
{ {
if( S_ISDIR(buf.st_mode) ) if( S_ISDIR(buf.st_mode) )
{ {
res = whole_path; res = wcsdup(dir);
break; break;
} }
else else
@ -412,17 +418,6 @@ wchar_t *path_allocate_cdpath( const wchar_t *dir )
err = ENOTDIR; err = ENOTDIR;
} }
} }
else
{
if( lwstat( whole_path, &buf ) == 0 )
{
err = EROTTEN;
}
}
free( whole_path );
}
free( path_cpy );
} }
if( !res ) if( !res )
@ -434,8 +429,8 @@ wchar_t *path_allocate_cdpath( const wchar_t *dir )
} }
bool path_can_get_cdpath(const wcstring &in) { bool path_can_get_cdpath(const wcstring &in, const wchar_t *wd) {
wchar_t *tmp = path_allocate_cdpath(in.c_str()); wchar_t *tmp = path_allocate_cdpath(in.c_str(), wd);
bool result = (tmp != NULL); bool result = (tmp != NULL);
free(tmp); free(tmp);
return result; return result;
@ -512,3 +507,37 @@ void path_make_canonical( wcstring &path )
path.resize(size+1); path.resize(size+1);
} }
bool path_is_valid(const wcstring &path, const wcstring &working_directory)
{
bool path_is_valid;
/* Some special paths are always valid */
if (path.empty()) {
path_is_valid = false;
} else if (path == L"." || path == L"./") {
path_is_valid = true;
} else if (path == L".." || path == L"../") {
path_is_valid = (! working_directory.empty() && working_directory != L"/");
} else if (path.at(0) != '/') {
/* Prepend the working directory. Note that we know path is not empty here. */
wcstring tmp = working_directory;
tmp.append(path);
path_is_valid = (0 == waccess(tmp.c_str(), F_OK));
} else {
/* Simple check */
path_is_valid = (0 == waccess(path.c_str(), F_OK));
}
return path_is_valid;
}
wcstring get_working_directory(void) {
wcstring wd = L"./";
wchar_t dir_path[4096];
const wchar_t *cwd = wgetcwd( dir_path, 4096 );
if (cwd) {
wd = cwd;
/* Make sure the working directory ends with a slash */
if (! wd.empty() && wd.at(wd.size() - 1) != L'/')
wd.push_back(L'/');
}
return wd;
}

8
path.h
View file

@ -49,11 +49,12 @@ bool path_get_path_string(const wcstring &cmd, wcstring &output, const env_vars
will be returned. will be returned.
\param in The name of the directory. \param in The name of the directory.
\param wd The working directory, or NULL to use the default. The working directory should have a slash appended at the end.
\return 0 if the command can not be found, the path of the command otherwise. The path should be free'd with free(). \return 0 if the command can not be found, the path of the command otherwise. The path should be free'd with free().
*/ */
wchar_t *path_allocate_cdpath( const wchar_t *in ); wchar_t *path_allocate_cdpath( const wchar_t *in, const wchar_t *wd = NULL);
bool path_can_get_cdpath(const wcstring &in); bool path_can_get_cdpath(const wcstring &in, const wchar_t *wd = NULL);
bool path_get_cdpath_string(const wcstring &in, wcstring &out, const env_vars &vars); bool path_get_cdpath_string(const wcstring &in, wcstring &out, const env_vars &vars);
/** /**
@ -62,5 +63,8 @@ bool path_get_cdpath_string(const wcstring &in, wcstring &out, const env_vars &v
*/ */
void path_make_canonical( wcstring &path ); void path_make_canonical( wcstring &path );
bool path_is_valid(const wcstring &path, const wcstring &working_directory);
wcstring get_working_directory(void);
#endif #endif

View file

@ -103,6 +103,7 @@ commence.
#include "screen.h" #include "screen.h"
#include "iothread.h" #include "iothread.h"
#include "intern.h" #include "intern.h"
#include "path.h"
#include "parse_util.h" #include "parse_util.h"
@ -1270,31 +1271,43 @@ static void run_pager( wchar_t *prefix, int is_quoted, const std::vector<complet
struct autosuggestion_context_t { struct autosuggestion_context_t {
history_search_t searcher; history_search_t searcher;
file_detection_context_t detector; file_detection_context_t detector;
const wcstring working_directory;
const env_vars vars;
autosuggestion_context_t(history_t *history, const wcstring &term) : autosuggestion_context_t(history_t *history, const wcstring &term) :
searcher(*history, term, HISTORY_SEARCH_TYPE_PREFIX), searcher(*history, term, HISTORY_SEARCH_TYPE_PREFIX),
detector(history, term) detector(history, term),
working_directory(get_working_directory()),
vars(env_vars::highlighting_keys)
{ {
} }
};
static int threaded_autosuggest(autosuggestion_context_t *ctx) { int threaded_autosuggest(void) {
ASSERT_IS_BACKGROUND_THREAD(); ASSERT_IS_BACKGROUND_THREAD();
while (ctx->searcher.go_backwards()) { while (searcher.go_backwards()) {
history_item_t item = ctx->searcher.current_item(); history_item_t item = searcher.current_item();
bool item_ok = false;
if (autosuggest_handle_special(item.str(), vars, working_directory, &item_ok)) {
/* The command autosuggestion was handled specially, so we're done */
} else {
/* See if the item has any required paths */ /* See if the item has any required paths */
bool item_ok;
const path_list_t &paths = item.get_required_paths(); const path_list_t &paths = item.get_required_paths();
if (paths.empty()) { if (paths.empty()) {
item_ok = true; item_ok = true;
} else { } else {
ctx->detector.potential_paths = paths; detector.potential_paths = paths;
item_ok = ctx->detector.paths_are_valid(paths); item_ok = detector.paths_are_valid(paths);
}
} }
if (item_ok) if (item_ok)
return 1; return 1;
} }
return 0; return 0;
}
};
static int threaded_autosuggest(autosuggestion_context_t *ctx) {
return ctx->threaded_autosuggest();
} }
static bool can_autosuggest(void) { static bool can_autosuggest(void) {
@ -2587,6 +2600,7 @@ static int read_i()
data->buff_pos=0; data->buff_pos=0;
data->command_line.clear(); data->command_line.clear();
data->check_size();
reader_run_command( parser, tmp ); reader_run_command( parser, tmp );
free( (void *)tmp ); free( (void *)tmp );
if( data->end_loop) if( data->end_loop)