From a08450bcb6050cc630d87ae6d7d5f203f8eeca62 Mon Sep 17 00:00:00 2001 From: ridiculousfish Date: Thu, 16 Feb 2012 00:24:27 -0800 Subject: [PATCH] Changes to make autosuggestion smarter about not suggesting commands that could never succeed. --- fish_tests.cpp | 68 ++++++- history.cpp | 526 +++++++++++++++++++++++++++++-------------------- history.h | 80 +++++++- parser.cpp | 4 +- path.cpp | 7 +- proc.cpp | 3 +- proc.h | 6 +- reader.cpp | 73 ++++++- util.h | 13 -- wutil.cpp | 6 + wutil.h | 5 + 11 files changed, 536 insertions(+), 255 deletions(-) diff --git a/fish_tests.cpp b/fish_tests.cpp index f206cd838..278b99c13 100644 --- a/fish_tests.cpp +++ b/fish_tests.cpp @@ -54,7 +54,8 @@ /** The number of tests to run */ -#define ESCAPE_TEST_COUNT 1000000 +//#define ESCAPE_TEST_COUNT 1000000 +#define ESCAPE_TEST_COUNT 10000 /** The average length of strings to unescape */ @@ -764,6 +765,7 @@ static void test_history_matches(history_search_t &search, size_t matches) { size_t i; for (i=0; i < matches; i++) { assert(search.go_backwards()); + wcstring item = search.current_string(); } assert(! search.go_backwards()); @@ -773,29 +775,79 @@ static void test_history_matches(history_search_t &search, size_t matches) { assert(! search.go_forwards()); } -static void test_history(void) { +class history_tests_t { +public: + static void test_history(void); +}; + +static wcstring random_string(void) { + wcstring result; + size_t max = 1 + rand() % 32; + while (max--) { + wchar_t c = 1 + rand()%ESCAPE_TEST_CHAR; + result.push_back(c); + } + return result; +} + +void history_tests_t::test_history(void) { say( L"Testing history"); history_t &history = history_t::history_with_name(L"test_history"); + history.clear(); history.add(L"Gamma"); history.add(L"Beta"); history.add(L"Alpha"); - /* All three items match "a" */ history_search_t search1(history, L"a"); test_history_matches(search1, 3); - assert(search1.current_item() == L"Alpha"); + assert(search1.current_string() == L"Alpha"); /* One item matches "et" */ history_search_t search2(history, L"et"); test_history_matches(search2, 1); - assert(search2.current_item() == L"Beta"); + assert(search2.current_string() == L"Beta"); + + /* Test history escaping and unescaping, yaml, etc. */ + std::vector before, after; + history.clear(); + size_t i, max = 100; + for (i=1; i <= max; i++) { + + /* Generate a value */ + wcstring value = wcstring(L"test item ") + format_val(i); + + /* Generate some paths */ + path_list_t paths; + size_t count = rand() % 6; + while (count--) { + paths.push_front(random_string()); + } + + /* Record this item */ + history_item_t item(value, time(NULL), paths); + before.push_back(item); + history.add(item); + } + history.save(); + + /* Read items back in reverse order and ensure they're the same */ + for (i=100; i >= 1; i--) { + history_item_t item = history.item_at_index(i); + assert(! item.empty()); + after.push_back(item); + } + assert(before.size() == after.size()); + for (size_t i=0; i < before.size(); i++) { + const history_item_t &bef = before.at(i), &aft = after.at(i); + assert(bef.contents == aft.contents); + assert(bef.creation_timestamp == aft.creation_timestamp); + assert(bef.required_paths == aft.required_paths); + } } - - /** Main test */ @@ -825,7 +877,7 @@ int main( int argc, char **argv ) test_expand(); test_path(); test_colors(); - test_history(); + history_tests_t::test_history(); say( L"Encountered %d errors in low-level tests", err_count ); diff --git a/history.cpp b/history.cpp index 476a4c7b6..73c594184 100644 --- a/history.cpp +++ b/history.cpp @@ -33,6 +33,21 @@ #include #include +/* + +Our history format is intended to be valid YAML. Here it is: + + - cmd: ssh blah blah blah + when: 2348237 + paths: + - /path/to/something + - /path/to/something_else + + Newlines are replaced by \n. Backslashes are replaced by \\. +*/ + + + /** When we rewrite the history, the number of items we keep */ #define HISTORY_SAVE_MAX (1024 * 256) @@ -46,9 +61,15 @@ class history_lru_node_t : public lru_node_t { public: time_t timestamp; - history_lru_node_t(const history_item_t &item) : lru_node_t(item.str()), timestamp(item.timestamp()) {} + path_list_t required_paths; + history_lru_node_t(const history_item_t &item) : + lru_node_t(item.str()), + timestamp(item.timestamp()), + required_paths(item.required_paths) + {} bool write_to_file(FILE *f) const; + bool write_yaml_to_file(FILE *f) const; }; class history_lru_cache_t : public lru_cache_t { @@ -72,6 +93,7 @@ class history_lru_cache_t : public lru_cache_t { history_lru_node_t *node = this->get_node(item.str()); if (node != NULL) { node->timestamp = std::max(node->timestamp, item.timestamp()); + /* What to do about paths here? Let's just ignore them */ } else { node = new history_lru_node_t(item); this->add_node(node); @@ -85,11 +107,11 @@ static std::map histories; static wcstring history_filename(const wcstring &name, const wcstring &suffix); -/* Unescapes newlines in-place */ -static void unescape_newlines(wcstring &str); +/** Replaces newlines with a literal backslash followed by an n, and replaces backslashes with two backslashes. */ +static void escape_yaml(std::string &str); -/* Escapes newlines in-place */ -static void escape_newlines(wcstring &str); +/** Undoes escape_yaml */ +static void unescape_yaml(std::string &str); /* Custom deleter for our shared_ptr */ class history_item_data_deleter_t { @@ -128,10 +150,29 @@ bool history_item_t::matches_search(const wcstring &term, enum history_search_ty } } -bool history_lru_node_t::write_to_file(FILE *f) const { - wcstring escaped = key; - escape_newlines(escaped); - return fwprintf( f, L"# %d\n%ls\n", timestamp, escaped.c_str()); +/* Output our YAML to a file */ +bool history_lru_node_t::write_yaml_to_file(FILE *f) const { + std::string cmd = wcs2string(key); + escape_yaml(cmd); + if (fprintf(f, "- cmd: %s\n", cmd.c_str()) < 0) + return false; + + if (fprintf(f, " when: %ld\n", (long)timestamp) < 0) + return false; + + if (! required_paths.empty()) { + if (fputs(" paths:\n", f) < 0) + return false; + + for (path_list_t::const_iterator iter = required_paths.begin(); iter != required_paths.end(); iter++) { + std::string path = wcs2string(*iter); + escape_yaml(path); + + if (fprintf(f, " - %s\n", path.c_str()) < 0) + return false; + } + } + return true; } history_t & history_t::history_with_name(const wcstring &name) { @@ -158,17 +199,27 @@ history_t::~history_t() pthread_mutex_destroy(&lock); } -void history_t::add(const wcstring &str, const path_list_t &valid_paths) +void history_t::add(const history_item_t &item) { scoped_lock locker(lock); /* Add the history items */ + new_items.push_back(item); + + /* Prevent the first write from always triggering a save */ time_t now = time(NULL); - new_items.push_back(history_item_t(str, now, valid_paths)); + if (! save_timestamp) + save_timestamp = now; /* This might be a good candidate for moving to a background thread */ if((now > save_timestamp + SAVE_INTERVAL) || (new_items.size() >= SAVE_COUNT)) this->save_internal(); + +} + +void history_t::add(const wcstring &str, const path_list_t &valid_paths) +{ + this->add(history_item_t(str, time(NULL), valid_paths)); } history_item_t history_t::item_at_index(size_t idx) { @@ -198,136 +249,151 @@ history_item_t history_t::item_at_index(size_t idx) { return history_item_t(wcstring(), 0); } -history_item_t history_t::decode_item(const char *begin, size_t len) -{ - const char *pos = begin, *end = begin + len; - - int was_backslash = 0; - - wcstring output; - time_t timestamp = 0; - - int first_char = 1; - bool timestamp_mode = false; - - while( 1 ) - { - wchar_t c; - mbstate_t state; - bzero(&state, sizeof state); - - size_t res; - - res = mbrtowc( &c, pos, end-pos, &state ); - - if( res == (size_t)-1 ) - { - pos++; - continue; - } - else if( res == (size_t)-2 ) - { - break; - } - else if( res == (size_t)0 ) - { - pos++; - continue; - } - pos += res; - - if( c == L'\n' ) - { - if( timestamp_mode ) - { - const wchar_t *time_string = output.c_str(); - while( *time_string && !iswdigit(*time_string)) - time_string++; - errno=0; - - if( *time_string ) - { - time_t tm; - wchar_t *end; - - errno = 0; - tm = (time_t)wcstol( time_string, &end, 10 ); - - if( tm && !errno && !*end ) - { - timestamp = tm; - } - - } - - output.clear(); - timestamp_mode = 0; - continue; - } - if( !was_backslash ) - break; - } - - if( first_char ) - { - if( c == L'#' ) - timestamp_mode = 1; - } - - first_char = 0; - - output.push_back(c); - - was_backslash = ( (c == L'\\') && !was_backslash); +/* Read one line, stripping off any newline, and updating cursor. Note that our string is NOT null terminated; it's just a memory mapped file. */ +static size_t read_line(const char *base, size_t cursor, size_t len, std::string &result) { + /* Locate the newline */ + assert(cursor <= len); + const char *start = base + cursor; + const char *newline = (char *)memchr(start, '\n', len - cursor); + if (newline != NULL) { + /* We found a newline. */ + result.assign(start, newline - start); + /* Return the amount to advance the cursor; skip over the newline */ + return newline - start + 1; + } else { + /* We ran off the end */ + result.clear(); + return len - cursor; } +} + +/* Trims leading spaces in the given string, returning how many there were */ +static size_t trim_leading_spaces(std::string &str) { + size_t i = 0, max = str.size(); + while (i < max && str[i] == ' ') + i++; + str.erase(0, i); + return i; +} + +static bool extract_prefix(std::string &key, std::string &value, const std::string &line) { + size_t where = line.find(":"); + if (where != std::string::npos) { + key = line.substr(0, where); + + // skip a space after the : if necessary + size_t val_start = where + 1; + if (val_start < line.size() && line.at(val_start) == ' ') + val_start++; + value = line.substr(val_start); + } + return where != std::string::npos; +} + +history_item_t history_t::decode_item(const char *base, size_t len) { + wcstring cmd; + time_t when = 0; + path_list_t paths; + + size_t indent = 0, cursor = 0; + std::string key, value, line; + + /* Read the "- cmd:" line */ + size_t advance = read_line(base, cursor, len, line); + trim_leading_spaces(line); + if (! extract_prefix(key, value, line) || key != "- cmd") + goto done; + + cursor += advance; + cmd = str2wcstring(value.c_str()); + + /* Read the remaining lines */ + for (;;) { + /* Read a line */ + size_t advance = read_line(base, cursor, len, line); + + /* Count and trim leading spaces */ + size_t this_indent = trim_leading_spaces(line); + if (indent == 0) + indent = this_indent; + + if (this_indent == 0 || indent != this_indent) + break; + + if (! extract_prefix(key, value, line)) + break; + + /* We are definitely going to consume this line */ + unescape_yaml(value); + cursor += advance; + + if (key == "when") { + /* Parse an int from the timestamp */ + long tmp = 0; + if (sscanf(value.c_str(), "%ld", &tmp) > 0) { + when = tmp; + } + } else if (key == "paths") { + /* Read lines starting with " - " until we can't read any more */ + for (;;) { + size_t advance = read_line(base, cursor, len, line); + if (trim_leading_spaces(line) <= indent) + break; + + if (strncmp(line.c_str(), "- ", 2)) + break; + + /* We're going to consume this line */ + cursor += advance; + + + /* Skip the leading dash-space and then store this path it */ + line.erase(0, 2); + unescape_yaml(line); + paths.push_front(str2wcstring(line.c_str())); + } + } + } + /* Reverse the paths, since we pushed them to the front each time */ +done: + paths.reverse(); + return history_item_t(cmd, when, paths); - unescape_newlines(output); - return history_item_t(output, timestamp); } void history_t::populate_from_mmap(void) { const char *begin = mmap_start; - const char *end = begin + mmap_length; - const char *pos; - - int ignore_newline = 0; - int do_push = 1; - - for( pos = begin; pos current_item(); + return item.str(); +} + bool history_search_t::match_already_made(const wcstring &match) const { for (std::deque::const_iterator iter = prev_matches.begin(); iter != prev_matches.end(); iter++) { - if (iter->second == match) + if (iter->second.str() == match) return true; } return false; } - -static void replace_all(wcstring &str, const wchar_t *needle, const wchar_t *replacement) +static void replace_all(std::string &str, const char *needle, const char *replacement) { - size_t needle_len = wcslen(needle); + size_t needle_len = strlen(needle), replacement_len = strlen(replacement); size_t offset = 0; - while((offset = str.find(needle, offset)) != wcstring::npos) + while((offset = str.find(needle, offset)) != std::string::npos) { str.replace(offset, needle_len, replacement); - offset += needle_len; + offset += replacement_len; } } -static void unescape_newlines(wcstring &str) -{ - /* Replace instances of backslash + newline with just the newline */ - replace_all(str, L"\\\n", L"\n"); +static void escape_yaml(std::string &str) { + replace_all(str, "\\", "\\\\"); //replace one backslash with two + replace_all(str, "\n", "\\n"); //replace newline with backslash + literal n } -static void escape_newlines(wcstring &str) -{ - /* Replace instances of newline with backslash + newline with newline */ - replace_all(str, L"\\\n", L"\n"); - - /* If the string ends with a backslash, we'll combine it with the next line. Hack around that by appending a newline. */ - if (! str.empty() && str.at(str.size() - 1) == L'\\') - str.push_back('\n'); +static void unescape_yaml(std::string &str) { + bool prev_escape = false; + for (size_t idx = 0; idx < str.size(); idx++) { + char c = str.at(idx); + if (prev_escape) { + if (c == '\\') { + /* Two backslashes in a row. Delete this one */ + str.erase(idx, 1); + idx--; + } else if (c == 'n') { + /* Replace backslash + n with an actual newline */ + str.replace(idx - 1, 2, "\n"); + idx--; + } + prev_escape = false; + } else { + prev_escape = (c == '\\'); + } + } } static wcstring history_filename(const wcstring &name, const wcstring &suffix) @@ -484,6 +564,20 @@ static wcstring history_filename(const wcstring &name, const wcstring &suffix) return result; } +void history_t::clear_file_state() +{ + /* Erase everything we know about our file */ + if (mmap_start != NULL && mmap_start != MAP_FAILED) { + munmap((void *)mmap_start, mmap_length); + } + mmap_start = 0; + mmap_length = 0; + loaded_old = false; + new_items.clear(); + old_item_offsets.clear(); + save_timestamp=time(0); +} + /** Save the specified mode to file */ void history_t::save_internal() { @@ -511,7 +605,7 @@ void history_t::save_internal() history_lru_cache_t lru(HISTORY_SAVE_MAX); /* Insert old items in, from old to new */ - for (std::vector::iterator iter = old_item_offsets.begin(); iter != old_item_offsets.end(); iter++) { + for (std::deque::iterator iter = old_item_offsets.begin(); iter != old_item_offsets.end(); iter++) { size_t offset = *iter; history_item_t item = history_t::decode_item(mmap_start + offset, mmap_length - offset); lru.add_item(item); @@ -524,7 +618,8 @@ void history_t::save_internal() /* Write them out */ for (history_lru_cache_t::iterator iter = lru.begin(); iter != lru.end(); iter++) { - if (! (*iter)->write_to_file(out)) { + const history_lru_node_t *node = *iter; + if (! node->write_yaml_to_file(out)) { ok = false; break; } @@ -550,15 +645,7 @@ void history_t::save_internal() if( ok ) { /* Our history has been written to the file, so clear our state so we can re-reference the file. */ - if (mmap_start != NULL && mmap_start != MAP_FAILED) { - munmap((void *)mmap_start, mmap_length); - } - mmap_start = 0; - mmap_length = 0; - loaded_old = false; - new_items.clear(); - old_item_offsets.clear(); - save_timestamp=time(0); + this->clear_file_state(); } signal_unblock(); @@ -569,6 +656,17 @@ void history_t::save(void) { this->save_internal(); } +void history_t::clear(void) { + scoped_lock locker(lock); + new_items.clear(); + old_item_offsets.clear(); + wcstring filename = history_filename(name, L""); + if (! filename.empty()) + wunlink(filename.c_str()); + this->clear_file_state(); + +} + void history_init() { } @@ -590,46 +688,69 @@ void history_sanity_check() */ } -struct file_detection_context_t { - /* The history associated with this context */ - history_t *history; - - /* The command */ - wcstring command; - - /* The working directory at the time the command was issued */ - wcstring working_directory; - - /* Paths to test */ - path_list_t potential_paths; - - /* Paths that were found to be valid */ - path_list_t valid_paths; - - int perform_file_detection() { - ASSERT_IS_BACKGROUND_THREAD(); - for (path_list_t::const_iterator iter = potential_paths.begin(); iter != potential_paths.end(); iter++) { - wcstring path = *iter; - +int file_detection_context_t::perform_file_detection(bool test_all) { + ASSERT_IS_BACKGROUND_THREAD(); + valid_paths.clear(); + int result = 1; + for (path_list_t::const_iterator iter = potential_paths.begin(); iter != potential_paths.end(); iter++) { + wcstring path = *iter; + + 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); } - - if (0 == waccess(path.c_str(), F_OK)) { - /* Push the original (possibly relative) path */ - valid_paths.push_front(*iter); - } + path_is_valid = (0 == waccess(path.c_str(), F_OK)); + } + + + if (path_is_valid) { + /* Push the original (possibly relative) path */ + valid_paths.push_front(*iter); + } else { + /* Not a valid path */ + result = 0; + if (! test_all) + break; } - valid_paths.reverse(); - return 0; } -}; + valid_paths.reverse(); + return result; +} + +bool file_detection_context_t::paths_are_valid(const path_list_t &paths) { + this->potential_paths = paths; + return perform_file_detection(false) > 0; +} + +file_detection_context_t::file_detection_context_t(history_t *hist, const wcstring &cmd) : + history(hist), + command(cmd), + when(time(NULL)) { + /* Stash the working directory. TODO: We should be respecting CDPATH here*/ + 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) { ASSERT_IS_BACKGROUND_THREAD(); assert(ctx != NULL); - return ctx->perform_file_detection(); + return ctx->perform_file_detection(true /* test all */); } static void perform_file_detection_done(file_detection_context_t *ctx, int success) { @@ -671,21 +792,8 @@ void history_t::add_with_file_detection(const wcstring &str) if (! potential_paths.empty()) { /* We have some paths. Make a context. */ - file_detection_context_t *context = new file_detection_context_t(); - context->command = str; - context->history = this; - - /* Stash the 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'/'); - context->working_directory.swap(wd); - } - + file_detection_context_t *context = new file_detection_context_t(this, str); + /* Store the potential paths. Reverse them to put them in the same order as in the command. */ potential_paths.reverse(); context->potential_paths.swap(potential_paths); diff --git a/history.h b/history.h index 952c91298..227bcaca5 100644 --- a/history.h +++ b/history.h @@ -28,10 +28,12 @@ enum history_search_type_t { class history_item_t { friend class history_t; + friend class history_lru_node_t; + friend class history_tests_t; private: - history_item_t(const wcstring &); - history_item_t(const wcstring &, time_t, const path_list_t &paths = path_list_t()); + explicit history_item_t(const wcstring &); + explicit history_item_t(const wcstring &, time_t, const path_list_t &paths = path_list_t()); /** The actual contents of the entry */ wcstring contents; @@ -51,10 +53,22 @@ class history_item_t { time_t timestamp() const { return creation_timestamp; } + const path_list_t &get_required_paths() const { return required_paths; } + bool write_to_file(FILE *f) const; + + bool operator==(const history_item_t &other) const { + return contents == other.contents && + creation_timestamp == other.creation_timestamp && + required_paths == other.required_paths; + } + + /* Functions for testing only */ + }; class history_t { + friend class history_tests_t; private: /** No copying */ history_t(const history_t&); @@ -63,11 +77,17 @@ private: /** Private creator */ history_t(const wcstring &pname); + /** Privately add an item */ + void add(const history_item_t &item); + /** Destructor */ ~history_t(); /** Lock for thread safety */ pthread_mutex_t lock; + + /** Internal function */ + void clear_file_state(); /** The name of this list. Used for picking a suitable filename and for switching modes. */ const wcstring name; @@ -91,7 +111,7 @@ private: void populate_from_mmap(void); /** List of old items, as offsets into out mmap data */ - std::vector old_item_offsets; + std::deque old_item_offsets; /** Whether we've loaded old items */ bool loaded_old; @@ -101,7 +121,7 @@ private: /** Saves history */ void save_internal(); - + public: /** Returns history with the given name, creating it if necessary */ static history_t & history_with_name(const wcstring &name); @@ -115,7 +135,10 @@ public: /** Saves history */ void save(); - /** Return the specified history at the specified index. 0 is the index of the current commandline. */ + /** Irreversibly clears history */ + void clear(); + + /** Return the specified history at the specified index. 0 is the index of the current commandline. (So the most recent item is at index 1.) */ history_item_t item_at_index(size_t idx); }; @@ -128,7 +151,7 @@ class history_search_t { enum history_search_type_t search_type; /** Our list of previous matches as index, value. The end is the current match. */ - typedef std::pair prev_match_t; + typedef std::pair prev_match_t; std::deque prev_matches; /** Returns yes if a given term is in prev_matches. */ @@ -143,6 +166,9 @@ class history_search_t { bool should_skip_match(const wcstring &str) const; public: + + /** Gets the search term */ + const wcstring &get_term() const { return term; } /** Sets additional string matches to skip */ void skip_matches(const wcstring_list_t &skips); @@ -162,8 +188,12 @@ class history_search_t { /** Goes to the beginning (backwards) */ void go_to_beginning(void); - /** Returns the current search result. asserts if there is no current item. */ - wcstring current_item(void) const; + /** Returns the current search result item. asserts if there is no current item. */ + history_item_t current_item(void) const; + + /** Returns the current search result item contents. asserts if there is no current item. */ + wcstring current_string(void) const; + /** Constructor */ history_search_t(history_t &hist, const wcstring &str, enum history_search_type_t type = HISTORY_SEARCH_TYPE_CONTAINS) : @@ -199,4 +229,38 @@ void history_destroy(); */ void history_sanity_check(); +/* A helper class for threaded detection of paths */ +struct file_detection_context_t { + + /* Constructor */ + file_detection_context_t(history_t *hist, const wcstring &cmd); + + /* Determine which of potential_paths are valid, and put them in valid_paths */ + int perform_file_detection(); + + /* The history associated with this context */ + history_t *history; + + /* The command */ + wcstring command; + + /* When the command was issued */ + time_t when; + + /* The working directory at the time the command was issued */ + wcstring working_directory; + + /* Paths to test */ + path_list_t potential_paths; + + /* Paths that were found to be valid */ + path_list_t valid_paths; + + /* Performs file detection. Returns 1 if every path in potential_paths is valid, 0 otherwise. If test_all is true, tests every path; otherwise stops as soon as it reaches an invalid path. */ + int perform_file_detection(bool test_all); + + /* Determine whether the given paths are all valid */ + bool paths_are_valid(const path_list_t &paths); +}; + #endif diff --git a/parser.cpp b/parser.cpp index 409eb6246..e5cc65c2e 100644 --- a/parser.cpp +++ b/parser.cpp @@ -716,7 +716,7 @@ void parser_t::destroy() lineinfo = 0; } - forbidden_function.resize(0); + forbidden_function.clear(); } @@ -2409,7 +2409,7 @@ int parser_t::eval( const wcstring &cmdStr, io_data_t *io, enum block_type_t blo if( block_type == SUBST ) { - forbidden_function.resize(0); + forbidden_function.clear(); } CHECK_BLOCK( 1 ); diff --git a/path.cpp b/path.cpp index bac23c745..8a2d26e1e 100644 --- a/path.cpp +++ b/path.cpp @@ -497,10 +497,13 @@ void path_make_canonical( wcstring &path ) { /* Remove double slashes */ - replace_all(path, L"//", L"/"); + size_t size; + do { + size = path.size(); + replace_all(path, L"//", L"/"); + } while (path.size() != size); /* Remove trailing slashes */ - size_t size = path.size(); while (size--) { if (path.at(size) != L'/') break; diff --git a/proc.cpp b/proc.cpp index 76fb7a694..0d959fe49 100644 --- a/proc.cpp +++ b/proc.cpp @@ -566,8 +566,9 @@ void proc_fire_event( const wchar_t *msg, int type, pid_t pid, int status ) event.arguments->resize(0); } -int job_reap( int interactive ) +int job_reap( bool interactive ) { + ASSERT_IS_MAIN_THREAD(); job_t *jnext; int found=0; diff --git a/proc.h b/proc.h index a995af901..4f439cecf 100644 --- a/proc.h +++ b/proc.h @@ -475,9 +475,7 @@ void job_free( job_t* j ); void job_promote(job_t *job); /** - Create a new job. Job struct is allocated using halloc, so anything - that should be freed with the job can uset it as a halloc context - when allocating. + Create a new job. */ job_t *job_create(); @@ -519,7 +517,7 @@ void job_continue( job_t *j, int cont ); \param interactive whether interactive jobs should be reaped as well */ -int job_reap( int interactive ); +int job_reap( bool interactive ); /** Signal handler for SIGCHLD. Mark any processes with relevant diff --git a/reader.cpp b/reader.cpp index f77ccc702..d7dd08d05 100644 --- a/reader.cpp +++ b/reader.cpp @@ -1283,15 +1283,67 @@ static void run_pager( wchar_t *prefix, int is_quoted, const std::vectorsearcher.go_backwards()) { + history_item_t item = ctx->searcher.current_item(); + /* See if the item has any required paths */ + bool item_ok; + const path_list_t &paths = item.get_required_paths(); + if (paths.empty()) { + item_ok = true; + } else { + ctx->detector.potential_paths = paths; + item_ok = ctx->detector.paths_are_valid(paths); + } + if (item_ok) + return 1; + } + return 0; +} + +static bool can_autosuggest(void) { + return ! data->suppress_autosuggestion && ! data->command_line.empty() && data->history_search.is_at_end(); +} + +static void autosuggest_completed(autosuggestion_context_t *ctx, int result) { + if (result && can_autosuggest() && ctx->searcher.get_term() == data->command_line) { + /* Autosuggestion is active and the search term has not changed, so we're good to go */ + data->autosuggestion = ctx->searcher.current_string(); + } + delete ctx; +} + + static void update_autosuggestion(void) { /* Updates autosuggestion. We look for an autosuggestion if the command line is non-empty and if we're not doing a history search. */ +#if 0 + /* Old non-threaded mode */ data->autosuggestion.clear(); - if (! data->suppress_autosuggestion && ! data->command_line.empty() && data->history_search.is_at_end()) { + if (can_autosuggest()) { history_search_t searcher = history_search_t(*data->history, data->command_line, HISTORY_SEARCH_TYPE_PREFIX); if (searcher.go_backwards()) { data->autosuggestion = searcher.current_item(); } } +#else + data->autosuggestion.clear(); + if (! data->suppress_autosuggestion && ! data->command_line.empty() && data->history_search.is_at_end()) { + autosuggestion_context_t *ctx = new autosuggestion_context_t(data->history, data->command_line); + iothread_perform(threaded_autosuggest, autosuggest_completed, ctx); + } +#endif } /** @@ -1317,8 +1369,6 @@ static void reader_flash() reader_super_highlight_me_plenty( data->buff_pos, 0 ); reader_repaint(); - - } /** @@ -1853,8 +1903,8 @@ static void handle_token_history( int forward, int reset ) Search for previous item that contains this substring */ if (data->history_search.go_backwards()) { - wcstring item = data->history_search.current_item(); - data->token_history_buff = data->history_search.current_item(); + wcstring item = data->history_search.current_string(); + data->token_history_buff = data->history_search.current_string(); } current_pos = data->token_history_buff.size(); @@ -2435,7 +2485,14 @@ static void reader_super_highlight_me_plenty( int match_highlight_pos, array_lis data->highlight_function( ctx->buff, ctx->color, match_highlight_pos, error, highlight_complete2, ctx ); #endif highlight_search(); - update_autosuggestion(); + + /* Here's a hack. Check to see if our autosuggestion still applies; if so, don't recompute it. Since the autosuggestion computation is asynchronous, this avoids "flashing" as you type into the autosuggestion. */ + const wcstring &cmd = data->command_line, &suggest = data->autosuggestion; + if (! suggest.empty() && ! cmd.empty() && string_prefixes_string(cmd, suggest)) { + /* The autosuggestion is still reasonable, so do nothing */ + } else { + update_autosuggestion(); + } } @@ -3019,7 +3076,7 @@ const wchar_t *reader_readline() if( ! data->command_line.empty() ) { if (data->history) { - data->history->add(data->command_line); + //data->history->add(data->command_line); data->history->add_with_file_detection(data->command_line); } } @@ -3108,7 +3165,7 @@ const wchar_t *reader_readline() if (data->history_search.is_at_end()) { new_text = data->search_buff; } else { - new_text = data->history_search.current_item(); + new_text = data->history_search.current_string(); } handle_history(new_text); diff --git a/util.h b/util.h index ae3771dd9..652107775 100644 --- a/util.h +++ b/util.h @@ -449,19 +449,6 @@ void al_foreach( array_list_t *l, void (*func)( void * )); */ void al_foreach2( array_list_t *l, void (*func)( void *, void *), void *aux); -template -T al_list_to(array_list_t *list) -{ - T result; - int i, c = al_get_count(list); - for (i=0; i < c; i++) { - void *val = al_get(list, i); - result.push_back(val); - } - return result; -} - - /** Compares two wide character strings with an (arguably) intuitive ordering. diff --git a/wutil.cpp b/wutil.cpp index e552df61f..3621f13d7 100644 --- a/wutil.cpp +++ b/wutil.cpp @@ -191,6 +191,12 @@ int waccess(const wchar_t *file_name, int mode) return access(tmp.c_str(), mode); } +int wunlink(const wchar_t *file_name) +{ + cstring tmp = wcs2string(file_name); + return unlink(tmp.c_str()); +} + void wperror(const wchar_t *s) { int e = errno; diff --git a/wutil.h b/wutil.h index c462b03c8..ddd7248f7 100644 --- a/wutil.h +++ b/wutil.h @@ -82,6 +82,11 @@ int lwstat(const wchar_t *file_name, struct stat *buf); */ int waccess(const wchar_t *pathname, int mode); +/** + Wide character version of unlink(). +*/ +int wunlink(const wchar_t *pathname); + /** Wide character version of perror(). */