From d5c382bb1a68fd74d5d8e96519f9286f8d755530 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Mon, 20 Feb 2012 02:13:31 -0800 Subject: [PATCH] Piling on more code to make autosuggestion try to guess directories even when they're not in the history --- highlight.cpp | 160 +++++++++++++++++++++++++++++++++++++++++++++++++- highlight.h | 1 + reader.cpp | 25 +++++++- wutil.cpp | 35 +++++++++-- wutil.h | 3 +- 5 files changed, 213 insertions(+), 11 deletions(-) diff --git a/highlight.cpp b/highlight.cpp index 338f2720e..62f036289 100644 --- a/highlight.cpp +++ b/highlight.cpp @@ -68,10 +68,11 @@ static const wchar_t * const highlight_var[] = Tests if the specified string is the prefix of any valid path in the system. \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, bool require_dir = false ) +static bool is_potential_path( const wcstring &cpath, wcstring *out_path = NULL, bool require_dir = false ) { ASSERT_IS_BACKGROUND_THREAD(); @@ -129,9 +130,11 @@ static bool is_potential_path( const wcstring &cpath, bool require_dir = false ) if( must_be_full_dir ) { dir = wopendir( cleaned_path ); - res = !!dir; if( dir ) { + res = true; + if (out_path) + *out_path = cleaned_path; closedir( dir ); } } @@ -143,16 +146,27 @@ static bool is_potential_path( const wcstring &cpath, bool require_dir = false ) if( dir_name == L"/" && base_name == L"/" ) { res = true; + if (out_path) + *out_path = cleaned_path; } else if( (dir = wopendir( dir_name)) ) { wcstring ent; bool is_dir; - while (wreaddir(dir, ent, &is_dir)) + while (wreaddir_resolving(dir, dir_name, ent, &is_dir)) { if (string_prefixes_string(base_name, ent) && (! require_dir || is_dir)) { res = true; + if (out_path) { + out_path->assign(dir_name); + out_path->push_back(L'/'); + out_path->append(ent); + path_make_canonical(*out_path); + /* We actually do want a trailing / for directories, since it makes autosuggestion a bit nicer */ + if (is_dir) + out_path->push_back(L'/'); + } break; } } @@ -528,6 +542,146 @@ static int has_expand_reserved( const wchar_t *str ) return 0; } +bool autosuggest_suggest_special(const wcstring &str, const wcstring &working_directory, wcstring &outString) { + if (str.empty()) + return false; + + wcstring cmd; + bool had_cmd = false; + + wcstring suggestion; + 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 ); + wcstring suggested_path; + if (is_potential_path(dir, &suggested_path, true /* require directory */)) { + suggestionOK = true; + suggestion = str; + suggestion.erase(tok_get_pos(&tok)); + suggestion.append(suggested_path); + } + } + } + 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 (suggestionOK) + outString.swap(suggestion); + return suggestionOK; +} + bool autosuggest_handle_special(const wcstring &str, const wcstring &working_directory, bool *outSuggestionOK) { ASSERT_IS_BACKGROUND_THREAD(); assert(outSuggestionOK != NULL); diff --git a/highlight.h b/highlight.h index e23c7f9ad..3b0eedbc6 100644 --- a/highlight.h +++ b/highlight.h @@ -107,6 +107,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 ); bool autosuggest_handle_special(const wcstring &str, const wcstring &working_directory, bool *outSuggestionOK); +bool autosuggest_suggest_special(const wcstring &str, const wcstring &working_directory, wcstring &outString); #endif diff --git a/reader.cpp b/reader.cpp index d69af92e1..4093cc4dd 100644 --- a/reader.cpp +++ b/reader.cpp @@ -1269,12 +1269,15 @@ static void run_pager( wchar_t *prefix, int is_quoted, const std::vectorautosuggestion = searcher.current_string(); return 1; + } } + + /* Since we didn't find a suggestion from history, try other means */ + wcstring special_suggestion; + if (autosuggest_suggest_special(search_string, working_directory, special_suggestion)) { + this->autosuggestion = special_suggestion; + return 1; + } + return 0; } }; @@ -1315,9 +1328,15 @@ static bool can_autosuggest(void) { } static void autosuggest_completed(autosuggestion_context_t *ctx, int result) { - if (result && can_autosuggest() && ctx->searcher.get_term() == data->command_line) { + if (result && + can_autosuggest() && + ctx->search_string == data->command_line && + string_prefixes_string(ctx->search_string, ctx->autosuggestion)) { /* Autosuggestion is active and the search term has not changed, so we're good to go */ - data->autosuggestion = ctx->searcher.current_string(); + data->autosuggestion = ctx->autosuggestion; + sanity_check(); + reader_repaint(); + } delete ctx; } diff --git a/wutil.cpp b/wutil.cpp index a95988370..748ee0103 100644 --- a/wutil.cpp +++ b/wutil.cpp @@ -82,14 +82,41 @@ void wutil_destroy() { } -bool wreaddir(DIR *dir, std::wstring &outPath, bool *is_dir) +bool wreaddir_resolving(DIR *dir, const std::wstring &dir_path, std::wstring &out_name, bool *out_is_dir) { struct dirent *d = readdir( dir ); if ( !d ) return false; - outPath = str2wcstring(d->d_name); - if (is_dir) - *is_dir = (d->d_type == DT_DIR); + out_name = str2wcstring(d->d_name); + if (out_is_dir) { + bool is_dir; + if (d->d_type == DT_DIR) { + is_dir = true; + } else if (d->d_type == DT_LNK) { + /* We want to treat symlinks to directories as directories. Use stat to resolve it. */ + cstring fullpath = wcs2string(dir_path); + fullpath.push_back('/'); + fullpath.append(d->d_name); + struct stat buf; + if (stat(fullpath.c_str(), &buf) != 0) { + is_dir = false; + } else { + is_dir = !! (S_ISDIR(buf.st_mode)); + } + } else { + is_dir = false; + } + *out_is_dir = is_dir; + } + return true; +} + +bool wreaddir(DIR *dir, std::wstring &out_name) +{ + struct dirent *d = readdir( dir ); + if ( !d ) return false; + + out_name = str2wcstring(d->d_name); return true; } diff --git a/wutil.h b/wutil.h index 7742d45ef..e2043023c 100644 --- a/wutil.h +++ b/wutil.h @@ -113,7 +113,8 @@ wchar_t *wrealpath(const wcstring &pathname, wchar_t *resolved_path); /** Wide character version of readdir() */ -bool wreaddir(DIR *dir, std::wstring &outPath, bool *outIsDirectory = NULL); +bool wreaddir(DIR *dir, std::wstring &out_name); +bool wreaddir_resolving(DIR *dir, const std::wstring &dir_path, std::wstring &out_name, bool *out_is_dir); /** Wide character version of dirname()