diff options
author | ridiculousfish <corydoras@ridiculousfish.com> | 2012-02-05 16:42:24 -0800 |
---|---|---|
committer | ridiculousfish <corydoras@ridiculousfish.com> | 2012-02-05 16:42:24 -0800 |
commit | 5ad6849d4e6aa76a72b671b50b143ef80d381a75 (patch) | |
tree | b381f5b239085d378af1865d82f549e33ba9827f /history.cpp | |
parent | 7fcf25a78f7241b9ee50ea9d5cbf2b0646c45b78 (diff) |
Work on new history implementation
Diffstat (limited to 'history.cpp')
-rw-r--r-- | history.cpp | 496 |
1 files changed, 473 insertions, 23 deletions
diff --git a/history.cpp b/history.cpp index 3bf3acb9..04cafdb4 100644 --- a/history.cpp +++ b/history.cpp @@ -28,7 +28,327 @@ #include "intern.h" #include "path.h" #include "signal.h" +#include <map> +static pthread_mutex_t hist_lock = PTHREAD_MUTEX_INITIALIZER; + +static std::map<wcstring, history_t *> histories; + +static wcstring history_filename(const wcstring &name, const wcstring &suffix); + +static hash_table_t *mode_table=0; + +/* Unescapes newlines in-place */ +static void unescape_newlines(wcstring &str); + +/* Custom deleter for our shared_ptr */ +class history_item_data_deleter_t { + private: + const bool free_it; + public: + history_item_data_deleter_t(bool flag) : free_it(flag) { } + void operator()(const wchar_t *data) { + if (free_it) + free((void *)data); + } +}; + +history_item_t::history_item_t(const wcstring &str) : contents(str), timestamp(time(NULL)) +{ +} + +history_item_t::history_item_t(const wcstring &str, time_t when) : contents(str), timestamp(when) +{ +} + + + +history_t & history_t::history_with_name(const wcstring &name) { + /* Note that histories are currently never deleted, so we can return a reference to them without using something like shared_ptr */ + scoped_lock locker(hist_lock); + history_t *& current = histories[name]; + if (current == NULL) + current = new history_t(name); + return *current; +} + +history_t::history_t(const wcstring &pname) : + name(pname), + mmap_start(NULL), + mmap_length(0), + save_timestamp(0), + loaded_old(false) +{ + pthread_mutex_init(&lock, NULL); +} + +history_t::~history_t() +{ + pthread_mutex_destroy(&lock); +} + +void history_t::add(const wcstring &str) +{ + scoped_lock locker(lock); + new_items.push_back(history_item_t(str.c_str(), true)); +} + +history_item_t history_t::item_at_index(size_t idx) { + scoped_lock locker(lock); + + /* 0 is considered an invalid index */ + assert(idx > 0); + idx--; + + /* idx=0 corresponds to last item in new_items */ + size_t new_item_count = new_items.size(); + if (idx < new_item_count) { + return new_items.at(new_item_count - idx - 1); + } + + /* Now look in our old items */ + idx -= new_item_count; + load_old_if_needed(); + size_t old_item_count = old_item_offsets.size(); + if (idx < old_item_count) { + /* idx=0 corresponds to last item in old_item_offsets */ + size_t offset = old_item_offsets.at(old_item_count - idx - 1); + return history_t::decode_item(mmap_start + offset, mmap_length - offset); + } + + /* Index past the valid range, so return an empty history item */ + 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); + + } + + 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 <end; pos++ ) + { + + if( do_push ) + { + ignore_newline = *pos == '#'; + /* Need to unique-ize */ + old_item_offsets.push_back(pos - begin); + do_push = 0; + } + + switch( *pos ) + { + case '\\': + { + pos++; + break; + } + + case '\n': + { + if( ignore_newline ) + { + ignore_newline = 0; + } + else + { + do_push = 1; + } + break; + } + } + } +} + +void history_t::load_old_if_needed(void) +{ + if (loaded_old) return; + loaded_old = true; + + int fd; + int ok=0; + + signal_block(); + wcstring filename = history_filename(name, L""); + + if( ! filename.empty() ) + { + if( ( fd = wopen( filename.c_str(), O_RDONLY ) ) > 0 ) + { + off_t len = lseek( fd, 0, SEEK_END ); + if( len != (off_t)-1) + { + mmap_length = (size_t)len; + if( lseek( fd, 0, SEEK_SET ) == 0 ) + { + if( (mmap_start = (char *)mmap( 0, mmap_length, PROT_READ, MAP_PRIVATE, fd, 0 )) != MAP_FAILED ) + { + ok = 1; + this->populate_from_mmap(); + } + } + } + close( fd ); + } + } + signal_unblock(); +} + +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; +} + +bool history_search_t::go_backwards() { + /* Backwards means increasing our index */ + const size_t max_idx = (size_t)(-1); + 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); + /* 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; + return true; + } + } + return false; +} + +/** Goes to the end (forwards) */ +void history_search_t::go_to_end(void) { + idx = 0; +} + +/** Goes to the beginning (backwards) */ +void history_search_t::go_to_beginning(void) { + idx = (size_t)(-1); +} + + +history_item_t history_search_t::current_item() const { + assert(idx > 0 && idx < (size_t)(-1)); + return history->item_at_index(idx); +} /** Interval in seconds between automatic history save @@ -125,13 +445,8 @@ typedef struct Original creation time for the entry */ time_t timestamp; -} - item_t; +} item_t; -/** - Table of all history modes -*/ -static hash_table_t *mode_table=0; /** The surrent history mode @@ -214,6 +529,19 @@ static wchar_t *history_escape_newlines( wchar_t *in ) return (wchar_t *)out->buff; } +static void unescape_newlines(wcstring &str) +{ + /* Replace instances of backslash + newline with just the newline */ + const wchar_t *needle = L"\\\n", *replacement = L"\n"; + size_t needle_len = wcslen(needle); + size_t offset = 0; + while((offset = str.find(needle, offset)) != wcstring::npos) + { + str.replace(offset, needle_len, replacement); + offset += needle_len; + } +} + /** Remove backslashes from all newlines. This makes a string from the history file better formated for on screen display. The memory for @@ -479,6 +807,20 @@ static wchar_t *history_filename( void *context, const wchar_t *name, const wcha return res; } +static wcstring history_filename(const wcstring &name, const wcstring &suffix) +{ + wcstring path; + if (! path_get_config(path)) + return L""; + + wcstring result = path; + result.append(L"/"); + result.append(name); + result.append(L"_history"); + result.append(suffix); + return result; +} + /** Go through the mmaped region and insert pointers to suitable loacations into the item list */ @@ -607,6 +949,131 @@ static void history_load( history_mode_t *m ) signal_unblock(); } +#if 0 +/** + Save the specified mode to file +*/ +void history_t::save( void *n, history_mode_t *m ) +{ + scoped_lock locker(lock); + + /* Nothing to do if there's no new items */ + if (new_items.empty()) + return; + + FILE *out; + int i; + int has_new=0; + wchar_t *tmp_name; + + int ok = 1; + + signal_block(); + + wcstring tmp_name = history_filename(name, L".tmp"); + + if( ! tmp_name.empty() ) + { + if( (out=wfopen( tmp_name, "w" ) ) ) + { + hash_table_t mine; + + hash_init( &mine, &hash_item_func, &hash_item_cmp ); + + for( i=0; i<al_get_count(&m->item); 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 && (i<al_get_count(&on_disk->item)); 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 && (i<al_get_count(&m->item)); 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(); +} +#endif + /** Save the specified mode to file */ @@ -951,23 +1418,6 @@ const wchar_t *history_next_match( const wchar_t *needle) } -void history_set_mode( const wchar_t *name ) -{ - if( !mode_table ) - { - mode_table = (hash_table_t *)malloc( sizeof(hash_table_t )); - hash_init( mode_table, &hash_wcs_func, &hash_wcs_cmp ); - } - - current_mode = (history_mode_t *)hash_get( mode_table, name ); - - if( !current_mode ) - { - current_mode = history_create_mode( name ); - hash_put( mode_table, name, current_mode ); - } -} - void history_init() { } |