diff --git a/history.cpp b/history.cpp index 50fb18ba8..33659996d 100644 --- a/history.cpp +++ b/history.cpp @@ -45,9 +45,9 @@ 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()) - { - } + history_lru_node_t(const history_item_t &item) : lru_node_t(item.str()), timestamp(item.timestamp()) {} + + bool write_to_file(FILE *f) const; }; class history_lru_cache_t : public lru_cache_t { @@ -110,10 +110,10 @@ history_item_t::history_item_t(const wcstring &str, time_t when) : contents(str) { } -bool history_item_t::write_to_file(FILE *f) const { - wcstring escaped = contents; +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", creation_timestamp, escaped.c_str()); + return fwprintf( f, L"# %d\n%ls\n", timestamp, escaped.c_str()); } history_t & history_t::history_with_name(const wcstring &name) { @@ -143,6 +143,7 @@ history_t::~history_t() void history_t::add(const wcstring &str) { scoped_lock locker(lock); + new_items.push_back(history_item_t(str.c_str(), true)); /* This might be a good candidate for moving to a background thread */ @@ -344,47 +345,33 @@ void history_t::load_old_if_needed(void) } bool history_search_t::go_forwards() { - /* Forwards means reducing our index. */ - if (idx == 0) - return false; - - size_t i; - for (i=idx-1; i > 0; i--) { - const history_item_t item = history->item_at_index(i); - /* Skip if it's empty. Empty items only occur at the end of the history. */ - if (item.empty()) - continue; - - /* Look for term in item.data */ - if (item.matches_search(term)) { - idx = i; - break; - } - } - return i > 0; + /* Pop the top index (if any) and return if we have any left */ + if (! prev_matches.empty()) + prev_matches.pop_back(); + return ! prev_matches.empty(); } bool history_search_t::go_backwards() { /* Backwards means increasing our index */ const size_t max_idx = (size_t)(-1); + + size_t idx = 0; + if (! prev_matches.empty()) + idx = prev_matches.back().first; + if (idx == max_idx) return false; - - size_t i; - for (i=idx+1;; i++) { - /* We're done if we reach the largest index */ - if (i == max_idx) - return false; - - const history_item_t item = history->item_at_index(i); + + while (++idx < max_idx) { + const history_item_t item = history->item_at_index(idx); /* We're done if it's empty */ if (item.empty()) { return false; } - /* Look for term in item.data */ - if (item.matches_search(term)) { - idx = i; + /* Look for a term that matches and that we haven't seen before */ + if (item.matches_search(term) && ! match_already_made(item.str())) { + prev_matches.push_back(prev_match_t(idx, item.str())); return true; } } @@ -393,189 +380,30 @@ bool history_search_t::go_backwards() { /** Goes to the end (forwards) */ void history_search_t::go_to_end(void) { - idx = 0; + prev_matches.clear(); } /** Goes to the beginning (backwards) */ void history_search_t::go_to_beginning(void) { - idx = (size_t)(-1); + /* Just go backwards as far as we can */ + while (go_backwards()) + ; } -history_item_t history_search_t::current_item() const { - assert(idx > 0 && idx < (size_t)(-1)); - return history->item_at_index(idx); +wcstring history_search_t::current_item() const { + assert(! prev_matches.empty()); + return prev_matches.back().second; } - -/** - A struct representiong a history list -*/ -typedef struct -{ - /** - The name of this list. Used for picking a suitable filename and for switching modes. - */ - const wchar_t *name; - - /** - The items of the list. Each entry may be either a pointer to an - item_t struct or a pointer to an mmaped memory region where a - multibyte string representing the item is stored. Use the - item_get function to always get an item_t. - */ - array_list_t item; - - - /** - A hash table containing all the items created by the current - session as keys. This can be used as a lookup when loading the - history list to ignore the on-file version of an entry from - this session. - */ - hash_table_t session_item; - - /** - The current history position - */ - int pos; - - /** - This flag is set nonzero if the file containing earlier saves has ben mmaped in - */ - int has_loaded; - - /** - The mmaped region for the history file - */ - char *mmap_start; - - /** - The size of the mmaped region - */ - size_t mmap_length; - - /** - A list of indices of all previous search maches. This is used to eliminate duplicate search results. - */ - array_list_t used; - - /** - Timestamp of last save - */ - time_t save_timestamp; - - /** - Number of entries that have been added since last save - */ - int new_count; - - /** - A halloc context. This context is free'd every time the history - is saved. Therefore it is very well suited for use as the - context for history item data. - */ - void *item_context; -} - history_mode_t; - -/** - This struct represents a history item -*/ -typedef struct -{ - /** - The actual contents of the entry - */ - wchar_t *data; - - /** - Original creation time for the entry - */ - time_t timestamp; -} item_t; - - -/** - The surrent history mode -*/ -static history_mode_t *current_mode=0; - -/** - Hash function for item_t struct -*/ -static int hash_item_func( void *v ) -{ - item_t *i = (item_t *)v; - return i->timestamp ^ hash_wcs_func( i->data ); +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) + return true; + } + return false; } -/** - Comparison function for item_t struct -*/ -static int hash_item_cmp( void *v1, void *v2 ) -{ - item_t *i1 = (item_t *)v1; - item_t *i2 = (item_t *)v2; - return (i1->timestamp == i2->timestamp) && (wcscmp( i1->data, i2->data )==0); -} - -/** - Add backslashes to all newlines, so that the returning string is - suitable for writing to the history file. The memory for the return - value will be reused by subsequent calls to this function. -*/ -static wchar_t *history_escape_newlines( wchar_t *in ) -{ - static string_buffer_t *out = 0; - if( !out ) - { - out = sb_halloc( global_context ); - if( !out ) - { - DIE_MEM(); - } - } - else - { - sb_clear( out ); - } - for( ; *in; in++ ) - { - if( *in == L'\\' ) - { - sb_append_char( out, *in ); - if( *(in+1) ) - { - in++; - sb_append_char( out, *in ); - } - else - { - /* - This is a weird special case. When we are trying to - save a string that ends with a backslash, we need to - handle it specially, otherwise this command would be - combined with the one following it. We hack around - this by adding an additional newline. - */ - sb_append_char( out, L'\n' ); - } - - } - else if( *in == L'\n' ) - { - sb_append_char( out, L'\\' ); - sb_append_char( out, *in ); - } - else - { - sb_append_char( out, *in ); - } - - } - return (wchar_t *)out->buff; -} static void replace_all(wcstring &str, const wchar_t *needle, const wchar_t *replacement) { @@ -604,271 +432,6 @@ static void escape_newlines(wcstring &str) str.push_back('\n'); } -/** - Remove backslashes from all newlines. This makes a string from the - history file better formated for on screen display. The memory for - the return value will be reused by subsequent calls to this - function. -*/ -static wchar_t *history_unescape_newlines( wchar_t *in ) -{ - static string_buffer_t *out = 0; - if( !out ) - { - out = sb_halloc( global_context ); - if( !out ) - { - DIE_MEM(); - } - } - else - { - sb_clear( out ); - } - for( ; *in; in++ ) - { - if( *in == L'\\' ) - { - if( *(in+1)!= L'\n') - { - sb_append_char( out, *in ); - } - } - else - { - sb_append_char( out, *in ); - } - } - return (wchar_t *)out->buff; -} - -/** - Check if the specified item is already loaded - */ -static int item_is_new( history_mode_t *m, void *d ) -{ - char *begin = (char *)d; - - if( !m->has_loaded || !m->mmap_start || (m->mmap_start == MAP_FAILED ) ) - { - return 1; - } - - if( (begin < m->mmap_start) || (begin > (m->mmap_start+m->mmap_length) ) ) - { - return 1; - } - return 0; -} - -/** - Returns an item_t for the specified adress. The adress must come from the item list of the specified mode. - - Later calls to this function may erase the output of a previous call to this function. -*/ -static item_t *item_get( history_mode_t *m, void *d ) -{ - char *begin = (char *)d; - - if( item_is_new( m, d ) ) - { - return (item_t *)d; - } - else - { - - char *end = m->mmap_start + m->mmap_length; - char *pos=begin; - - int was_backslash = 0; - static string_buffer_t *out = 0; - static item_t narrow_item; - int first_char = 1; - int timestamp_mode = 0; - - narrow_item.timestamp = 0; - - if( !out ) - { - out = sb_halloc( global_context ); - if( !out ) - { - DIE_MEM(); - } - } - else - { - sb_clear( out ); - } - - while( 1 ) - { - wchar_t c; - mbstate_t state; - size_t res; - - memset( &state, 0, sizeof(state) ); - - 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 ) - { - wchar_t *time_string = (wchar_t *)out->buff; - 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 ) - { - narrow_item.timestamp = tm; - } - - } - - sb_clear( out ); - timestamp_mode = 0; - continue; - } - if( !was_backslash ) - break; - } - - if( first_char ) - { - if( c == L'#' ) - timestamp_mode = 1; - } - - first_char = 0; - - sb_append_char( out, c ); - - was_backslash = ( (c == L'\\') && !was_backslash); - - } - - narrow_item.data = history_unescape_newlines((wchar_t *)out->buff); - return &narrow_item; - } - -} - -/** - Write the specified item to the specified file. -*/ -static int item_write( FILE *f, history_mode_t *m, void *v ) -{ - item_t *i = item_get( m, v ); - return fwprintf( f, L"# %d\n%ls\n", i->timestamp, history_escape_newlines( i->data ) ); -} - -/** - Release all memory used by the specified history mode. -*/ -static void history_destroy_mode( history_mode_t *m ) -{ - halloc_free( m->item_context ); - - if( m->mmap_start && (m->mmap_start != MAP_FAILED )) - { - munmap( m->mmap_start, m->mmap_length); - } - -} - -/** - Free all memory used by specified mistory mode - */ -static void history_destroy_mode_wrapper( void *n, history_mode_t *m ) -{ - halloc_free( m ); -} - -/** - Create a new empty mode with the specified name. - The mode is a halloc context, and the entire mode can be destroyed using halloc_free(). -*/ -static history_mode_t *history_create_mode( const wchar_t *name ) -{ - history_mode_t *new_mode = (history_mode_t *)halloc( 0, sizeof( history_mode_t )); - - new_mode->name = intern(name); - - al_init( &new_mode->item ); - al_init( &new_mode->used ); - halloc_register_function( new_mode, (void (*)(void *))&al_destroy, &new_mode->item ); - halloc_register_function( new_mode, (void (*)(void *))&al_destroy, &new_mode->used ); - - hash_init( &new_mode->session_item, &hash_item_func, &hash_item_cmp ); - halloc_register_function( new_mode, (void (*)(void *))&hash_destroy, &new_mode->session_item ); - - new_mode->save_timestamp=time(0); - new_mode->item_context = halloc( 0,0 ); - - halloc_register_function( new_mode, (void (*)(void *))&history_destroy_mode, new_mode ); - - return new_mode; -} - -/** - This function tests if the search string is a match for the given string -*/ -static int history_test( const wchar_t *needle, const wchar_t *haystack ) -{ - return !!wcsstr( haystack, needle ); -} - -/** - Returns the name of the save file for a mode. - - \param context a halloc context used to allocate memory - \param name the name of the hstory mode - \param suffix an optional file suffix -*/ -static wchar_t *history_filename( void *context, const wchar_t *name, const wchar_t *suffix ) -{ - wchar_t *path; - wchar_t *res; - - if( !current_mode ) - return 0; - - path = path_get_config( context ); - - if( !path ) - return 0; - - res = wcsdupcat( path, L"/", name, L"_history", suffix?suffix:NULL); - halloc_register_function( context, &free, res ); - return res; -} - static wcstring history_filename(const wcstring &name, const wcstring &suffix) { wcstring path; @@ -883,134 +446,6 @@ static wcstring history_filename(const wcstring &name, const wcstring &suffix) return result; } -/** - Go through the mmaped region and insert pointers to suitable loacations into the item list -*/ -static void history_populate_from_mmap( history_mode_t *m ) -{ - char *begin = m->mmap_start; - char *end = begin + m->mmap_length; - char *pos; - - array_list_t old_item; - array_list_t session_item_list; - int ignore_newline = 0; - int do_push = 1; - - al_init( &old_item ); - al_init( &session_item_list ); - al_push_all( &old_item, &m->item ); - al_truncate( &m->item, 0 ); - - for( pos = begin; pos session_item, i ) ) ) - { - /* - This item comes from this session. Insert the - original item at the end of the item list. - */ - al_push( &session_item_list, i_orig ); - } - else - { - /* - Old item. Insert pointer directly to the item list - */ - al_push( &m->item, pos ); - } - - do_push = 0; - } - - switch( *pos ) - { - case '\\': - { - pos++; - break; - } - - case '\n': - { - if( ignore_newline ) - { - ignore_newline = 0; - } - else - { - do_push = 1; - } - break; - } - } - } - - al_push_all( &m->item, &session_item_list ); - m->pos += al_get_count( &m->item ); - al_push_all( &m->item, &old_item ); - - - al_destroy( &session_item_list ); - al_destroy( &old_item ); -} - -/** - Load contents of the backing file to memory -*/ -static void history_load( history_mode_t *m ) -{ - int fd; - int ok=0; - - void *context; - wchar_t *filename; - - if( !m ) - return; - - m->has_loaded=1; - - signal_block(); - - context = halloc( 0, 0 ); - filename = history_filename( context, m->name, 0 ); - - if( filename ) - { - if( ( fd = wopen( filename, O_RDONLY ) ) > 0 ) - { - off_t len = lseek( fd, 0, SEEK_END ); - if( len != (off_t)-1) - { - m->mmap_length = (size_t)len; - if( lseek( fd, 0, SEEK_SET ) == 0 ) - { - if( (m->mmap_start = (char *)mmap( 0, m->mmap_length, PROT_READ, MAP_PRIVATE, fd, 0 )) != MAP_FAILED ) - { - ok = 1; - history_populate_from_mmap( m ); - } - } - } - close( fd ); - } - } - - halloc_free( context ); - signal_unblock(); -} - /** Save the specified mode to file */ void history_t::save_internal() { @@ -1051,12 +486,10 @@ void history_t::save_internal() /* Write them out */ for (history_lru_cache_t::iterator iter = lru.begin(); iter != lru.end(); iter++) { -#if 0 if (! (*iter)->write_to_file(out)) { ok = false; break; } -#endif } @@ -1098,350 +531,6 @@ void history_t::save(void) { this->save_internal(); } -/** - Save the specified mode to file -*/ -static void history_save_mode( void *n, history_mode_t *m ) -{ - FILE *out; - history_mode_t *on_disk; - int i; - int has_new=0; - wchar_t *tmp_name; - - int ok = 1; - - /* - First check if there are any new entries to save. If not, then - we can just return - */ - for( i=0; iitem); i++ ) - { - void *ptr = al_get( &m->item, i ); - has_new = item_is_new( m, ptr ); - if( has_new ) - { - break; - } - } - - if( !has_new ) - { - return; - } - - signal_block(); - - /* - Set up on_disk variable to describe the current contents of the - history file - */ - on_disk = history_create_mode( m->name ); - history_load( on_disk ); - - tmp_name = history_filename( on_disk, m->name, L".tmp" ); - - if( tmp_name ) - { - tmp_name = wcsdup(tmp_name ); - - if( (out=wfopen( tmp_name, "w" ) ) ) - { - hash_table_t mine; - - hash_init( &mine, &hash_item_func, &hash_item_cmp ); - - for( i=0; iitem); i++ ) - { - void *ptr = al_get( &m->item, i ); - int is_new = item_is_new( m, ptr ); - if( is_new ) - { - hash_put( &mine, item_get( m, ptr ), L"" ); - } - } - - /* - Re-save the old history - */ - for( i=0; ok && (iitem)); i++ ) - { - void *ptr = al_get( &on_disk->item, i ); - item_t *i = item_get( on_disk, ptr ); - if( !hash_get( &mine, i ) ) - { - if( item_write( out, on_disk, ptr ) == -1 ) - { - ok = 0; - break; - } - } - - } - - hash_destroy( &mine ); - - /* - Add our own items last - */ - for( i=0; ok && (iitem)); i++ ) - { - void *ptr = al_get( &m->item, i ); - int is_new = item_is_new( m, ptr ); - if( is_new ) - { - if( item_write( out, m, ptr ) == -1 ) - { - ok = 0; - } - } - } - - if( fclose( out ) || !ok ) - { - /* - This message does not have high enough priority to - be shown by default. - */ - debug( 2, L"Error when writing history file" ); - } - else - { - wrename( tmp_name, history_filename( on_disk, m->name, 0 ) ); - } - } - free( tmp_name ); - } - - halloc_free( on_disk); - - if( ok ) - { - - /* - Reset the history. The item_t entries created in this session - are not lost or dropped, they are stored in the session_item - hash table. On reload, they will be automatically inserted at - the end of the history list. - */ - - if( m->mmap_start && (m->mmap_start != MAP_FAILED ) ) - { - munmap( m->mmap_start, m->mmap_length ); - } - - al_truncate( &m->item, 0 ); - al_truncate( &m->used, 0 ); - m->pos = 0; - m->has_loaded = 0; - m->mmap_start=0; - m->mmap_length=0; - - m->save_timestamp=time(0); - m->new_count = 0; - } - - signal_unblock(); -} - - -void history_add( const wchar_t *str ) -{ - item_t *i; - - if( !current_mode ) - return; - - i = (item_t *)halloc( current_mode->item_context, sizeof(item_t)); - i->data = (wchar_t *)halloc_wcsdup( current_mode->item_context, str ); - i->timestamp = time(0); - - al_push( ¤t_mode->item, i ); - hash_put( ¤t_mode->session_item, i, i ); - - al_truncate( ¤t_mode->used, 0 ); - current_mode->pos = al_get_count( ¤t_mode->item ); - - current_mode->new_count++; - - if( (time(0) > current_mode->save_timestamp+SAVE_INTERVAL) || (current_mode->new_count >= SAVE_COUNT) ) - { - history_save_mode( 0, current_mode ); - } - -} - -/** - Check if the specified string has already been used a s a search match -*/ -static int history_is_used( const wchar_t *str ) -{ - int i; - int res =0; - item_t *it = 0; - - for( i=0; iused); i++ ) - { - long idx = al_get_long( ¤t_mode->used, i ); - it = item_get( current_mode, al_get( ¤t_mode->item, (int)idx ) ); - if( wcscmp( it->data, str ) == 0 ) - { - res = 1; - break; - } - - } - return res; -} - -const wchar_t *history_prev_match( const wchar_t *needle ) -{ - if( current_mode ) - { - if( current_mode->pos > 0 ) - { - for( current_mode->pos--; current_mode->pos>=0; current_mode->pos-- ) - { - item_t *i = item_get( current_mode, al_get( ¤t_mode->item, current_mode->pos ) ); - wchar_t *haystack = (wchar_t *)i->data; - - if( history_test( needle, haystack ) ) - { - int is_used; - - /* - This is ugly. Whenever we call item_get(), - there is a chance that the return value of any - previous call to item_get will become - invalid. The history_is_used function uses the - item_get() function. Therefore, we must create - a copy of the haystack string, and if the string - is unused, we must call item_get anew. - */ - - haystack = wcsdup(haystack ); - - is_used = history_is_used( haystack ); - - free( haystack ); - - if( !is_used ) - { - i = item_get( current_mode, al_get( ¤t_mode->item, current_mode->pos ) ); - al_push_long( ¤t_mode->used, current_mode->pos ); - return i->data; - } - } - } - } - - if( !current_mode->has_loaded ) - { - /* - We found no match in the list, try loading the history - file and continue the search - */ - history_load( current_mode ); - return history_prev_match( needle ); - } - else - { - /* - We found no match in the list, and the file is already - loaded. Set poition before first element and return - original search string. - */ - current_mode->pos=-1; - if( al_peek_long( ¤t_mode->used ) != -1 ) - al_push_long( ¤t_mode->used, -1 ); - } - - } - - return needle; -} - - -wchar_t *history_get( int idx ) -{ - int len; - - if( !current_mode ) - return 0; - - len = al_get_count( ¤t_mode->item ); - - if( (idx >= len ) && !current_mode->has_loaded ) - { - history_load( current_mode ); - len = al_get_count( ¤t_mode->item ); - } - - if( idx < 0 ) - return 0; - - if( idx >= len ) - return 0; - - return item_get( current_mode, al_get( ¤t_mode->item, len - 1 - idx ) )->data; -} - -void history_first() -{ - if( current_mode ) - { - if( !current_mode->has_loaded ) - { - history_load( current_mode ); - } - - current_mode->pos = 0; - } -} - -void history_reset() -{ - if( current_mode ) - { - current_mode->pos = al_get_count( ¤t_mode->item ); - /* - Clear list of search matches - */ - al_truncate( ¤t_mode->used, 0 ); - } -} - -const wchar_t *history_next_match( const wchar_t *needle) -{ - if( current_mode ) - { - /* - The index of previous search matches are saved in the 'used' - list. We just need to pop the top item and set the new - position. Easy! - */ - if( al_get_count( ¤t_mode->used ) ) - { - al_pop( ¤t_mode->used ); - if( al_get_count( ¤t_mode->used ) ) - { - current_mode->pos = (int) al_peek_long( ¤t_mode->used ); - item_t *i = item_get( current_mode, al_get( ¤t_mode->item, current_mode->pos ) ); - return i->data; - } - } - - /* - The used-list is empty. Set position to 'past end of list' - and return the search string. - */ - current_mode->pos = al_get_count( ¤t_mode->item ); - - } - return needle; -} - - void history_init() { } diff --git a/history.h b/history.h index 466c2b78a..9f1c1a6eb 100644 --- a/history.h +++ b/history.h @@ -9,6 +9,8 @@ #include "common.h" #include "pthread.h" #include +#include +#include #include using std::tr1::shared_ptr; @@ -100,14 +102,18 @@ class history_search_t { /** The history in which we are searching */ history_t * history; + + /** Our list of previous matches as index, value. The end is the current match. */ + typedef std::pair prev_match_t; + std::deque prev_matches; + + /** Returns yes if a given term is in prev_matches. */ + bool match_already_made(const wcstring &match) const; /** The search term */ wcstring term; - - /** Current index into the history */ - size_t idx; -public: + public: /** Finds the next search term (forwards in time). Returns true if one was found. */ bool go_forwards(void); @@ -122,20 +128,18 @@ public: void go_to_beginning(void); /** Returns the current search result. asserts if there is no current item. */ - history_item_t current_item(void) const; + wcstring current_item(void) const; /** Constructor */ history_search_t(history_t &hist, const wcstring &str) : history(&hist), - term(str), - idx() + term(str) {} /* Default constructor */ history_search_t() : history(), - term(), - idx() + term() {} }; diff --git a/reader.cpp b/reader.cpp index 95558a3aa..e83bb0f8f 100644 --- a/reader.cpp +++ b/reader.cpp @@ -1885,8 +1885,8 @@ static void handle_token_history( int forward, int reset ) /* No luck */ data->token_history_buff = wcsdup(L""); } else { - history_item_t item = data->history_search.current_item(); - data->token_history_buff = wcsdup(item.str().c_str()); + wcstring item = data->history_search.current_item(); + data->token_history_buff = wcsdup(item.c_str()); } current_pos = wcslen(data->token_history_buff); @@ -3131,7 +3131,7 @@ wchar_t *reader_readline() wcstring new_text; if (success) { - new_text = data->history_search.current_item().str(); + new_text = data->history_search.current_item(); } else { new_text = data->search_buff; }