From a515db4aea51a032b06eb463fcc5a5b70066a18c Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Fri, 24 Feb 2012 12:13:35 -0800 Subject: [PATCH] Some work to allow completions to be evaluated off of the main thread --- builtin_complete.cpp | 2 +- common.cpp | 12 ++++++- common.h | 3 ++ complete.cpp | 83 ++++++++++++++++++++++++-------------------- complete.h | 11 +++--- expand.cpp | 4 +-- expand.h | 62 +++++++++++++++------------------ reader.cpp | 12 ++++--- reader.h | 4 ++- wildcard.cpp | 42 ++++++++-------------- wildcard.h | 5 +-- wutil.cpp | 33 ++++++++++-------- 12 files changed, 144 insertions(+), 129 deletions(-) diff --git a/builtin_complete.cpp b/builtin_complete.cpp index 39b599205..3b9657cea 100644 --- a/builtin_complete.cpp +++ b/builtin_complete.cpp @@ -547,7 +547,7 @@ static int builtin_complete( parser_t &parser, wchar_t **argv ) { recursion_level++; - complete( do_complete, comp ); + complete( do_complete, comp, COMPLETE_DEFAULT ); for( size_t i=0; i< comp.size() ; i++ ) { diff --git a/common.cpp b/common.cpp index 13d085129..d8f76ab5c 100644 --- a/common.cpp +++ b/common.cpp @@ -2070,5 +2070,15 @@ void assert_is_background_thread(const char *who) if (is_main_thread()) { fprintf(stderr, "Warning: %s called on the main thread (may block!). Break on debug_thread_error to debug.\n", who); debug_thread_error(); - } + } +} + +void assert_is_locked(void *vmutex, const char *who) +{ + pthread_mutex_t *mutex = static_cast(vmutex); + if (0 == pthread_mutex_trylock(mutex)) { + fprintf(stderr, "Warning: %s is not locked when it should be. Break on debug_thread_error to debug.\n", who); + debug_thread_error(); + pthread_mutex_unlock(mutex); + } } diff --git a/common.h b/common.h index 9e74dc307..8fb950fe9 100644 --- a/common.h +++ b/common.h @@ -258,6 +258,9 @@ void assert_is_background_thread(const char *who); #define ASSERT_IS_BACKGROUND_THREAD_TRAMPOLINE(x) assert_is_background_thread(x) #define ASSERT_IS_BACKGROUND_THREAD() ASSERT_IS_BACKGROUND_THREAD_TRAMPOLINE(__FUNCTION__) +/* Useful macro for asserting that a lock is locked. This doesn't check whether this thread locked it, which it would be nice if it did, but here it is anyways. */ +void assert_is_locked(void *mutex, const char *who); +#define ASSERT_IS_LOCKED(x) assert_is_locked((void *)(&x), #x) /** Converts the wide character string \c in into it's narrow diff --git a/complete.cpp b/complete.cpp index c7d12f815..9457f4e8c 100644 --- a/complete.cpp +++ b/complete.cpp @@ -20,7 +20,7 @@ #include #include #include - +#include #include "fallback.h" #include "util.h" @@ -176,6 +176,7 @@ struct completion_entry_t /** Linked list of all completion entries */ typedef std::list completion_entry_list_t; static completion_entry_list_t completion_entries; +static pthread_mutex_t completion_lock = PTHREAD_MUTEX_INITIALIZER; /** Table of completions conditions that have already been tested and @@ -259,12 +260,10 @@ static int condition_test( const wcstring &condition ) } -/** - Search for an exactly matching completion entry -*/ -static completion_entry_t *complete_find_exact_entry( const wchar_t *cmd, - const int cmd_type ) +/** Search for an exactly matching completion entry. Must be called while locked. */ +static completion_entry_t *complete_find_exact_entry( const wchar_t *cmd, const int cmd_type ) { + ASSERT_IS_LOCKED(completion_lock); for (completion_entry_list_t::iterator iter = completion_entries.begin(); iter != completion_entries.end(); iter++) { completion_entry_t *entry = *iter; @@ -274,16 +273,14 @@ static completion_entry_t *complete_find_exact_entry( const wchar_t *cmd, return NULL; } -/** - Locate the specified entry. Create it if it doesn't exist. -*/ -static completion_entry_t *complete_get_exact_entry( const wchar_t *cmd, - int cmd_type ) +/** Locate the specified entry. Create it if it doesn't exist. Must be called while locked. */ +static completion_entry_t *complete_get_exact_entry( const wchar_t *cmd, int cmd_type ) { + ASSERT_IS_LOCKED(completion_lock); completion_entry_t *c; complete_init(); - + c = complete_find_exact_entry( cmd, cmd_type ); if( c == NULL ) @@ -307,6 +304,7 @@ void complete_set_authoritative( const wchar_t *cmd, completion_entry_t *c; CHECK( cmd, ); + scoped_lock lock(completion_lock); c = complete_get_exact_entry( cmd, cmd_type ); c->authoritative = authoritative; } @@ -324,7 +322,8 @@ void complete_add( const wchar_t *cmd, int flags ) { CHECK( cmd, ); - + + scoped_lock lock(completion_lock); completion_entry_t *c; c = complete_get_exact_entry( cmd, cmd_type ); @@ -356,11 +355,11 @@ void complete_add( const wchar_t *cmd, /** Remove all completion options in the specified entry that match the specified short / long option strings. Returns true if it is now - empty and should be deleted, false if it's not empty. + empty and should be deleted, false if it's not empty. Must be called while locked. */ static bool complete_remove_entry( completion_entry_t *e, wchar_t short_opt, const wchar_t *long_opt ) { - + ASSERT_IS_LOCKED(completion_lock); if(( short_opt == 0 ) && (long_opt == 0 ) ) { e->options.clear(); @@ -410,6 +409,7 @@ void complete_remove( const wchar_t *cmd, const wchar_t *long_opt ) { CHECK( cmd, ); + scoped_lock lock(completion_lock); for (completion_entry_list_t::iterator iter = completion_entries.begin(); iter != completion_entries.end(); ) { completion_entry_t *e = *iter; bool delete_it = false; @@ -529,7 +529,8 @@ int complete_is_valid_option( const wchar_t *str, Make sure completions are loaded for the specified command */ if (allow_autoload) complete_load( cmd, false ); - + + scoped_lock lock(completion_lock); for (completion_entry_list_t::iterator iter = completion_entries.begin(); iter != completion_entries.end(); iter++) { const completion_entry_t *i = *iter; @@ -741,6 +742,8 @@ static void complete_strings( std::vector &comp_out, */ static void complete_cmd_desc( const wchar_t *cmd, std::vector &comp ) { + ASSERT_IS_MAIN_THREAD(); + const wchar_t *cmd_start; int cmd_len; int skip; @@ -886,7 +889,8 @@ static void complete_cmd( const wchar_t *cmd, std::vector &comp, int use_function, int use_builtin, - int use_command ) + int use_command, + complete_type_t type ) { wchar_t *path_cpy; wchar_t *nxt_path; @@ -896,10 +900,12 @@ static void complete_cmd( const wchar_t *cmd, wchar_t *cdpath_cpy = wcsdup(L"."); + const bool wants_description = (type == COMPLETE_DEFAULT); + if( (wcschr( cmd, L'/') != 0) || (cmd[0] == L'~' ) ) { - if( use_command ) + if( use_command && wants_description ) { if( expand_string(cmd, comp, ACCEPT_INCOMPLETE | EXECUTABLES_ONLY ) != EXPAND_ERROR ) @@ -941,8 +947,7 @@ static void complete_cmd( const wchar_t *cmd, prev_count = comp.size() ; - if( expand_string( - nxt_completion, + if( expand_string( nxt_completion, comp, ACCEPT_INCOMPLETE | EXECUTABLES_ONLY ) != EXPAND_ERROR ) @@ -958,7 +963,8 @@ static void complete_cmd( const wchar_t *cmd, } } free( path_cpy ); - complete_cmd_desc( cmd, comp ); + if (wants_description) + complete_cmd_desc( cmd, comp ); } } @@ -1157,7 +1163,8 @@ static int complete_param( const wchar_t *cmd_orig, const wchar_t *popt, const wchar_t *str, int use_switches, - std::vector &comp_out ) + complete_type_t type, + std::vector &comp_out) { int use_common=1, use_files=1; @@ -1165,7 +1172,8 @@ static int complete_param( const wchar_t *cmd_orig, wcstring cmd, path; parse_cmd_string(cmd_orig, path, cmd); - complete_load( cmd, true ); + if (type == COMPLETE_DEFAULT) + complete_load( cmd, true ); for (completion_entry_list_t::iterator iter = completion_entries.begin(); iter != completion_entries.end(); iter++) { @@ -1362,9 +1370,7 @@ static int complete_param( const wchar_t *cmd_orig, /** Perform file completion on the specified string */ -static void complete_param_expand( const wchar_t *str, - std::vector &comp_out, - int do_file ) +static void complete_param_expand( const wchar_t *str, std::vector &comp_out, int do_file, complete_type_t type ) { const wchar_t *comp_str; int flags; @@ -1377,10 +1383,14 @@ static void complete_param_expand( const wchar_t *str, { comp_str = str; } - - flags = EXPAND_SKIP_CMDSUBST | - ACCEPT_INCOMPLETE | - (do_file?0:EXPAND_SKIP_WILDCARDS); + + flags = EXPAND_SKIP_CMDSUBST | ACCEPT_INCOMPLETE; + + if (! do_file) + flags |= EXPAND_SKIP_WILDCARDS; + + if (type == COMPLETE_AUTOSUGGEST) + flags |= EXPAND_NO_DESCRIPTIONS; if( expand_string( comp_str, comp_out, @@ -1553,8 +1563,7 @@ static int try_complete_user( const wchar_t *cmd, return res; } -void complete( const wchar_t *cmd, - std::vector &comp ) +void complete( const wchar_t *cmd, std::vector &comp, complete_type_t type ) { const wchar_t *tok_begin, *tok_end, *cmdsubst_begin, *cmdsubst_end, *prev_begin, *prev_end; @@ -1610,7 +1619,7 @@ void complete( const wchar_t *cmd, int had_cmd=0; int end_loop=0; - tok_init( &tok, buff.c_str(), TOK_ACCEPT_UNFINISHED ); + tok_init( &tok, buff.c_str(), TOK_ACCEPT_UNFINISHED | TOK_SQUASH_ERRORS ); while( tok_has_next( &tok) && !end_loop ) { @@ -1765,8 +1774,7 @@ void complete( const wchar_t *cmd, if( on_command ) { /* Complete command filename */ - complete_cmd( current_token, - comp, use_function, use_builtin, use_command ); + complete_cmd( current_token, comp, use_function, use_builtin, use_command, type ); } else { @@ -1781,7 +1789,8 @@ void complete( const wchar_t *cmd, do_file = complete_param( current_command_unescape, prev_token_unescape, current_token_unescape, - !had_ddash, + !had_ddash, + type, comp ); } @@ -1799,7 +1808,7 @@ void complete( const wchar_t *cmd, /* This function wants the unescaped string */ - complete_param_expand( current_token, comp, do_file ); + complete_param_expand( current_token, comp, do_file, type ); } } } diff --git a/complete.h b/complete.h index 1abc94497..f7964e2a1 100644 --- a/complete.h +++ b/complete.h @@ -148,6 +148,10 @@ public: bool operator != (const completion_t& rhs) const { return ! (*this == rhs); } }; +enum complete_type_t { + COMPLETE_DEFAULT, + COMPLETE_AUTOSUGGEST +}; /** @@ -218,10 +222,9 @@ void complete_remove( const wchar_t *cmd, wchar_t short_opt, const wchar_t *long_opt ); -/** - Find all completions of the command cmd, insert them into out. -*/ -void complete( const wchar_t* cmd, std::vector &out); + +/** Find all completions of the command cmd, insert them into out. */ +void complete( const wchar_t* cmd, std::vector &out, complete_type_t type); /** Print a list of all current completions into the string_buffer_t. diff --git a/expand.cpp b/expand.cpp index 497842b74..67253face 100644 --- a/expand.cpp +++ b/expand.cpp @@ -1429,7 +1429,7 @@ static void remove_internal_separator2( wcstring &s, int conv ) } -int expand_string( const wcstring &input, std::vector &output, int flags ) +int expand_string( const wcstring &input, std::vector &output, expand_flags_t flags ) { parser_t parser(PARSER_TYPE_ERRORS_ONLY); std::vector list1, list2; @@ -1639,7 +1639,7 @@ int expand_string( const wcstring &input, std::vector &output, int return res; } -bool expand_one(wcstring &string, int flags) { +bool expand_one(wcstring &string, expand_flags_t flags) { std::vector completions; bool result = false; diff --git a/expand.h b/expand.h index 1c40c0b1a..414768c74 100644 --- a/expand.h +++ b/expand.h @@ -21,40 +21,34 @@ #include "common.h" #include -/** - Flag specifying that cmdsubst expansion should be skipped -*/ -#define EXPAND_SKIP_CMDSUBST 1 +enum { + /** Flag specifying that cmdsubst expansion should be skipped */ + EXPAND_SKIP_CMDSUBST = 1 << 0, + + /** Flag specifying that variable expansion should be skipped */ + EXPAND_SKIP_VARIABLES = 1 << 1, + + /** Flag specifying that wildcard expansion should be skipped */ + EXPAND_SKIP_WILDCARDS = 1 << 2, -/** - Flag specifying that variable expansion should be skipped -*/ -#define EXPAND_SKIP_VARIABLES 2 + /** + Incomplete matches in the last segment are ok (for tab + completion). An incomplete match is a wildcard that matches a + prefix of the filename. If accept_incomplete is true, only the + remainder of the string is returned. + */ + ACCEPT_INCOMPLETE = 1 << 3, -/** - Flag specifying that wildcard expansion should be skipped -*/ -#define EXPAND_SKIP_WILDCARDS 4 - -/** - Incomplete matches in the last segment are ok (for tab - completion). An incomplete match is a wildcard that matches a - prefix of the filename. If accept_incomplete is true, only the - remainder of the string is returned. -*/ -#define ACCEPT_INCOMPLETE 8 - -/** - Only match files that are executable by the current user. Only applicable together with ACCEPT_INCOMPLETE. -*/ - -#define EXECUTABLES_ONLY 16 - -/** - Only match directories. Only applicable together with ACCEPT_INCOMPLETE. -*/ - -#define DIRECTORIES_ONLY 32 + /** Only match files that are executable by the current user. Only applicable together with ACCEPT_INCOMPLETE. */ + EXECUTABLES_ONLY = 1 << 4, + + /** Only match directories. Only applicable together with ACCEPT_INCOMPLETE. */ + DIRECTORIES_ONLY = 1 << 5, + + /** Don't generate descriptions */ + EXPAND_NO_DESCRIPTIONS = 1 << 6 +}; +typedef int expand_flags_t; /** Use unencoded private-use keycodes for internal characters @@ -143,7 +137,7 @@ class parser_t; \param flag Specifies if any expansion pass should be skipped. Legal values are any combination of EXPAND_SKIP_CMDSUBST EXPAND_SKIP_VARIABLES and EXPAND_SKIP_WILDCARDS \return One of EXPAND_OK, EXPAND_ERROR, EXPAND_WILDCARD_MATCH and EXPAND_WILDCARD_NO_MATCH. EXPAND_WILDCARD_NO_MATCH and EXPAND_WILDCARD_MATCH are normal exit conditions used only on strings containing wildcards to tell if the wildcard produced any matches. */ -__warn_unused int expand_string( const wcstring &input, std::vector &output, int flag ); +__warn_unused int expand_string( const wcstring &input, std::vector &output, expand_flags_t flags ); /** @@ -155,7 +149,7 @@ __warn_unused int expand_string( const wcstring &input, std::vector& ); + complete_function_t complete_func; /** Function for syntax highlighting @@ -1235,6 +1234,10 @@ struct autosuggestion_context_t { int threaded_autosuggest(void) { ASSERT_IS_BACKGROUND_THREAD(); + + std::vector completions; + complete(search_string.c_str(), completions, COMPLETE_AUTOSUGGEST); + while (searcher.go_backwards()) { history_item_t item = searcher.current_item(); bool item_ok = false; @@ -2299,8 +2302,7 @@ void reader_set_prompt( const wchar_t *new_prompt ) data->prompt = new_prompt; } -void reader_set_complete_function( void (*f)( const wchar_t *, - std::vector& ) ) +void reader_set_complete_function( complete_function_t f ) { data->complete_func = f; } @@ -2828,7 +2830,7 @@ const wchar_t *reader_readline() buffcpy = wcsndup( begin, len ); // comp = al_halloc( 0 ); - data->complete_func( buffcpy, comp ); + data->complete_func( buffcpy, comp, COMPLETE_DEFAULT ); sort_completion_list( comp ); remove_duplicates( comp ); diff --git a/reader.h b/reader.h index 9cb412e89..4acb95988 100644 --- a/reader.h +++ b/reader.h @@ -15,6 +15,7 @@ #include "util.h" #include "io.h" #include "common.h" +#include "complete.h" class parser_t; class completion_t; @@ -132,7 +133,8 @@ void reader_pop(); - The command to be completed as a null terminated array of wchar_t - An array_list_t in which completions will be inserted. */ -void reader_set_complete_function( void (*f)( const wchar_t *, std::vector & ) ); +typedef void (*complete_function_t)( const wchar_t *, std::vector &, complete_type_t ); +void reader_set_complete_function( complete_function_t ); /** The type of a highlight function. diff --git a/wildcard.cpp b/wildcard.cpp index d7028cf7e..0c77da421 100644 --- a/wildcard.cpp +++ b/wildcard.cpp @@ -573,7 +573,7 @@ static void wildcard_completion_allocate( std::vector &list, const wcstring &fullname, const wchar_t *completion, const wchar_t *wc, - int is_cmd ) + expand_flags_t expand_flags) { struct stat buf, lbuf; wcstring sb; @@ -625,8 +625,10 @@ static void wildcard_completion_allocate( std::vector &list, } } - wcstring desc = file_get_desc( fullname.c_str(), lstat_res, lbuf, stat_res, buf, stat_errno ); - + wcstring desc; + if (! (expand_flags & EXPAND_NO_DESCRIPTIONS)) + desc = file_get_desc( fullname.c_str(), lstat_res, lbuf, stat_res, buf, stat_errno ); + if( sz >= 0 && S_ISDIR(buf.st_mode) ) { free_completion = 1; @@ -688,7 +690,7 @@ static int test_flags( const wchar_t *filename, */ static int wildcard_expand_internal( const wchar_t *wc, const wchar_t *base_dir, - int flags, + expand_flags_t flags, std::vector &out ) { @@ -797,7 +799,7 @@ static int wildcard_expand_internal( const wchar_t *wc, long_name, next.c_str(), L"", - flags & EXECUTABLES_ONLY ); + flags); } } } @@ -842,7 +844,7 @@ static int wildcard_expand_internal( const wchar_t *wc, long_name, name, wc, - flags & EXECUTABLES_ONLY ); + flags); } } @@ -1058,7 +1060,7 @@ static int wildcard_expand_internal( const wchar_t *wc, int wildcard_expand( const wchar_t *wc, const wchar_t *base_dir, - int flags, + expand_flags_t flags, std::vector &out ) { size_t c = out.size(); @@ -1066,17 +1068,12 @@ int wildcard_expand( const wchar_t *wc, if( flags & ACCEPT_INCOMPLETE ) { - const wchar_t *wc_base=L""; + wcstring wc_base; const wchar_t *wc_base_ptr = wcsrchr( wc, L'/' ); - string_buffer_t sb; - - if( wc_base_ptr ) { - wc_base = wcsndup( wc, (wc_base_ptr-wc)+1 ); + wc_base = wcstring(wc, (wc_base_ptr-wc)+1); } - - sb_init( &sb ); for( size_t i=c; i &outputs ) +int wildcard_expand_string(const wcstring &wc, const wcstring &base_dir, expand_flags_t flags, std::vector &outputs ) { std::vector lst; diff --git a/wildcard.h b/wildcard.h index e6db70995..af15e9ab4 100644 --- a/wildcard.h +++ b/wildcard.h @@ -17,6 +17,7 @@ #include "util.h" #include "common.h" +#include "expand.h" /* Use unencoded private-use keycodes for internal characters @@ -66,7 +67,7 @@ enum \return 1 if matches where found, 0 otherwise. Return -1 on abort (I.e. ^C was pressed). */ -int wildcard_expand_string(const wcstring &wc, const wcstring &base_dir, int flags, std::vector &out ); +int wildcard_expand_string(const wcstring &wc, const wcstring &base_dir, expand_flags_t flags, std::vector &out ); /** Test whether the given wildcard matches the string @@ -90,6 +91,6 @@ int wildcard_complete( const wchar_t *str, const wchar_t *desc, const wchar_t *(*desc_func)(const wcstring &), std::vector &out, - int flags ); + expand_flags_t flags ); #endif diff --git a/wutil.cpp b/wutil.cpp index 748ee0103..f3642a531 100644 --- a/wutil.cpp +++ b/wutil.cpp @@ -20,6 +20,7 @@ #include #include #include +#include #if HAVE_LIBINTL_H @@ -73,6 +74,12 @@ static char *wcs2str_buff=0; */ static size_t wcs2str_buff_count=0; +/* Lock to protect wgettext */ +static pthread_mutex_t wgettext_lock; + +/* Maps string keys to (immortal) pointers to string values */ +typedef std::map wgettext_map_t; +static std::map wgettext_map; void wutil_init() { @@ -312,6 +319,7 @@ static void wgettext_really_init() { { sb_init( &buff[i] ); } + pthread_mutex_init(&wgettext_lock, NULL); bindtextdomain( PACKAGE_NAME, LOCALEDIR ); textdomain( PACKAGE_NAME ); } @@ -347,24 +355,21 @@ static char *wgettext_wcs2str( const wchar_t *in ) const wchar_t *wgettext( const wchar_t *in ) { - ASSERT_IS_MAIN_THREAD(); - if( !in ) return in; wgettext_init_if_necessary(); - - char *mbs_in = wgettext_wcs2str( in ); - char *out = gettext( mbs_in ); - wchar_t *wres=0; - - sb_clear( &buff[curr_buff] ); - - sb_printf( &buff[curr_buff], L"%s", out ); - wres = (wchar_t *)buff[curr_buff].buff; - curr_buff = (curr_buff+1)%BUFF_COUNT; - - return wres; + + wcstring key = in; + scoped_lock lock(wgettext_lock); + + wcstring *& val = wgettext_map[key]; + if (val == NULL) { + cstring mbs_in = wcs2string(key); + char *out = gettext(mbs_in.c_str()); + val = new wcstring(format_string(L"%s", out)); + } + return val->c_str(); } wcstring wgettext2(const wcstring &in) {