Changes to make autosuggestion smarter about not suggesting commands that could never succeed.

This commit is contained in:
ridiculousfish 2012-02-16 00:24:27 -08:00
parent a92d9d442b
commit a08450bcb6
11 changed files with 536 additions and 255 deletions

View File

@ -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<history_item_t> 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 );

View File

@ -33,6 +33,21 @@
#include <map>
#include <algorithm>
/*
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<history_lru_node_t> {
@ -72,6 +93,7 @@ class history_lru_cache_t : public lru_cache_t<history_lru_node_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<wcstring, history_t *> 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;
size_t cursor = 0;
while (cursor < mmap_length) {
const char *line_start = begin + cursor;
/* Look for a newline */
const char *newline = (const char *)memchr(line_start, '\n', mmap_length - cursor);
if (newline == NULL)
break;
int ignore_newline = 0;
int do_push = 1;
/* Advance the cursor past this line. +1 is for the newline */
size_t line_len = newline - line_start;
cursor += line_len + 1;
for( pos = begin; pos <end; pos++ )
{
/* Skip lines with a leading, since these are in the interior of one of our items */
if (line_start[0] == ' ')
continue;
if( do_push )
{
ignore_newline = *pos == '#';
/* Need to unique-ize */
old_item_offsets.push_back(pos - begin);
do_push = 0;
}
/* Skip very short lines to make one of the checks below easier */
if (line_len < 3)
continue;
switch( *pos )
{
case '\\':
{
pos++;
break;
}
/* Try to be a little YAML compatible. Skip lines with leading %, ---, or ... */
if (! memcmp(line_start, "%", 1) ||
! memcmp(line_start, "---", 3) ||
! memcmp(line_start, "...", 3))
continue;
case '\n':
{
if( ignore_newline )
{
ignore_newline = 0;
}
else
{
do_push = 1;
}
break;
}
}
}
/* We made it through the gauntlet. */
old_item_offsets.push_back(line_start - begin);
}
}
void history_t::load_old_if_needed(void)
@ -403,7 +469,7 @@ bool history_search_t::go_backwards() {
/* Look for a term that matches and that we haven't seen before */
const wcstring &str = item.str();
if (item.matches_search(term, search_type) && ! match_already_made(str) && ! should_skip_match(str)) {
prev_matches.push_back(prev_match_t(idx, item.str()));
prev_matches.push_back(prev_match_t(idx, item));
return true;
}
}
@ -429,45 +495,59 @@ void history_search_t::go_to_beginning(void) {
}
wcstring history_search_t::current_item() const {
history_item_t history_search_t::current_item() const {
assert(! prev_matches.empty());
return prev_matches.back().second;
}
wcstring history_search_t::current_string() const {
history_item_t item = this->current_item();
return item.str();
}
bool history_search_t::match_already_made(const wcstring &match) const {
for (std::deque<prev_match_t>::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<size_t>::iterator iter = old_item_offsets.begin(); iter != old_item_offsets.end(); iter++) {
for (std::deque<size_t>::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,20 +792,7 @@ 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();

View File

@ -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,12 +77,18 @@ 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<size_t> old_item_offsets;
std::deque<size_t> old_item_offsets;
/** Whether we've loaded old items */
bool loaded_old;
@ -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<size_t, wcstring> prev_match_t;
typedef std::pair<size_t, history_item_t> prev_match_t;
std::deque<prev_match_t> prev_matches;
/** Returns yes if a given term is in prev_matches. */
@ -144,6 +167,9 @@ class history_search_t {
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

View File

@ -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 );

View File

@ -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;

View File

@ -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;

6
proc.h
View File

@ -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

View File

@ -1283,15 +1283,67 @@ static void run_pager( wchar_t *prefix, int is_quoted, const std::vector<complet
io_buffer_destroy( in);
}
struct autosuggestion_context_t {
history_search_t searcher;
file_detection_context_t detector;
autosuggestion_context_t(history_t *history, const wcstring &term) :
searcher(*history, term, HISTORY_SEARCH_TYPE_PREFIX),
detector(history, term)
{
}
};
static int threaded_autosuggest(autosuggestion_context_t *ctx) {
ASSERT_IS_BACKGROUND_THREAD();
while (ctx->searcher.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);

13
util.h
View File

@ -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<typename T>
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.

View File

@ -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;

View File

@ -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().
*/