2005-09-20 23:26:39 +10:00
/** \file history.c
2007-01-20 12:33:47 +10:00
History functions , part of the user interface .
2005-09-20 23:26:39 +10:00
*/
2006-08-11 11:18:35 +10:00
# include "config.h"
2005-09-20 23:26:39 +10:00
# include <stdlib.h>
# include <stdio.h>
# include <wchar.h>
# include <errno.h>
# include <dirent.h>
# include <sys/types.h>
# include <sys/stat.h>
# include <unistd.h>
2006-10-21 08:36:49 +10:00
# include <sys/mman.h>
# include <fcntl.h>
# include <string.h>
# include <time.h>
# include <assert.h>
2005-09-20 23:26:39 +10:00
2006-02-28 23:17:16 +10:00
# include "fallback.h"
2005-09-20 23:26:39 +10:00
# include "util.h"
2006-02-28 23:17:16 +10:00
2005-09-20 23:26:39 +10:00
# include "wutil.h"
# include "history.h"
# include "common.h"
2006-09-08 22:34:55 +10:00
# include "halloc.h"
# include "halloc_util.h"
2006-10-21 08:36:49 +10:00
# include "intern.h"
2006-10-19 21:50:23 +10:00
# include "path.h"
2006-10-21 08:36:49 +10:00
# include "signal.h"
2012-02-05 20:54:41 -08:00
# include "autoload.h"
2012-02-05 16:42:24 -08:00
# include <map>
2012-02-05 20:54:41 -08:00
# include <algorithm>
/** When we rewrite the history, the number of items we keep */
# define HISTORY_SAVE_MAX (1024 * 256)
/** Interval in seconds between automatic history save */
# define SAVE_INTERVAL (5*60)
/** Number of new history entries to add before automatic history save */
# define SAVE_COUNT 5
/* Our LRU cache is used for restricting the amount of history we have, and limiting how long we order it. */
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 ( ) )
{
}
} ;
class history_lru_cache_t : public lru_cache_t < history_lru_node_t > {
protected :
/* Override to delete evicted nodes */
virtual void node_was_evicted ( history_lru_node_t * node ) {
delete node ;
}
public :
history_lru_cache_t ( size_t max ) : lru_cache_t < history_lru_node_t > ( max ) { }
/* Function to add a history item */
void add_item ( const history_item_t & item ) {
/* Skip empty items */
if ( item . empty ( ) )
return ;
/* See if it's in the cache. If it is, update the timestamp. If not, we create a new node and add it. Note that calling get_node promotes the node to the front. */
history_lru_node_t * node = this - > get_node ( item . str ( ) ) ;
if ( node ! = NULL ) {
node - > timestamp = std : : max ( node - > timestamp , item . timestamp ( ) ) ;
} else {
node = new history_lru_node_t ( item ) ;
this - > add_node ( node ) ;
}
}
} ;
2005-09-20 23:26:39 +10:00
2012-02-05 16:42:24 -08:00
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 ) ;
/* Unescapes newlines in-place */
static void unescape_newlines ( wcstring & str ) ;
2012-02-05 20:54:41 -08:00
/* Escapes newlines in-place */
static void escape_newlines ( wcstring & str ) ;
2012-02-05 16:42:24 -08:00
/* 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 ) ;
}
} ;
2012-02-05 20:54:41 -08:00
history_item_t : : history_item_t ( const wcstring & str ) : contents ( str ) , creation_timestamp ( time ( NULL ) )
2012-02-05 16:42:24 -08:00
{
}
2012-02-05 20:54:41 -08:00
history_item_t : : history_item_t ( const wcstring & str , time_t when ) : contents ( str ) , creation_timestamp ( when )
2012-02-05 16:42:24 -08:00
{
}
2012-02-05 20:54:41 -08:00
bool history_item_t : : write_to_file ( FILE * f ) const {
wcstring escaped = contents ;
escape_newlines ( escaped ) ;
return fwprintf ( f , L " # %d \n %ls \n " , creation_timestamp , escaped . c_str ( ) ) ;
}
2012-02-05 16:42:24 -08:00
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 ) ) ;
2012-02-05 20:54:41 -08:00
/* This might be a good candidate for moving to a background thread */
if ( ( time ( 0 ) > save_timestamp + SAVE_INTERVAL ) | | ( new_items . size ( ) > = SAVE_COUNT ) )
this - > save_internal ( ) ;
2012-02-05 16:42:24 -08:00
}
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 ) ;
}
2005-09-20 23:26:39 +10:00
2006-10-21 09:04:20 +10:00
/**
A struct representiong a history list
*/
2011-12-26 19:18:46 -08:00
typedef struct
2006-09-08 22:34:55 +10:00
{
2006-10-21 09:04:20 +10:00
/**
The name of this list . Used for picking a suitable filename and for switching modes .
*/
2006-10-21 08:36:49 +10:00
const wchar_t * name ;
2011-12-26 19:18:46 -08:00
2006-10-21 09:04:20 +10:00
/**
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 .
*/
2006-10-21 08:36:49 +10:00
array_list_t item ;
2006-10-21 09:04:20 +10:00
2006-10-22 11:21:02 +10:00
/**
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 ;
2011-12-26 19:18:46 -08:00
2006-10-21 09:04:20 +10:00
/**
The current history position
*/
2006-10-21 08:36:49 +10:00
int pos ;
2011-12-26 19:18:46 -08:00
2006-10-21 09:04:20 +10:00
/**
This flag is set nonzero if the file containing earlier saves has ben mmaped in
*/
2006-10-21 08:36:49 +10:00
int has_loaded ;
2006-10-21 09:04:20 +10:00
/**
The mmaped region for the history file
*/
2006-10-21 08:36:49 +10:00
char * mmap_start ;
2006-10-21 09:04:20 +10:00
/**
The size of the mmaped region
*/
2006-10-21 08:36:49 +10:00
size_t mmap_length ;
2006-10-21 09:04:20 +10:00
/**
A list of indices of all previous search maches . This is used to eliminate duplicate search results .
*/
2006-10-22 11:21:02 +10:00
array_list_t used ;
2011-12-26 19:18:46 -08:00
2006-10-21 09:04:20 +10:00
/**
Timestamp of last save
*/
2006-10-21 08:36:49 +10:00
time_t save_timestamp ;
2006-10-21 09:04:20 +10:00
/**
Number of entries that have been added since last save
*/
2006-10-21 08:36:49 +10:00
int new_count ;
2006-10-21 09:04:20 +10:00
/**
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 .
*/
2006-10-21 08:36:49 +10:00
void * item_context ;
2006-09-08 22:34:55 +10:00
}
2006-10-21 08:36:49 +10:00
history_mode_t ;
2006-09-08 22:34:55 +10:00
2006-10-21 09:04:20 +10:00
/**
This struct represents a history item
*/
2011-12-26 19:18:46 -08:00
typedef struct
2006-09-08 22:34:55 +10:00
{
2006-10-21 09:04:20 +10:00
/**
The actual contents of the entry
*/
2006-10-21 08:36:49 +10:00
wchar_t * data ;
2011-12-26 19:18:46 -08:00
2006-10-21 09:04:20 +10:00
/**
Original creation time for the entry
*/
2006-10-21 08:36:49 +10:00
time_t timestamp ;
2012-02-05 16:42:24 -08:00
} item_t ;
2006-10-21 08:36:49 +10:00
2006-10-21 09:04:20 +10:00
/**
The surrent history mode
*/
2006-10-21 08:36:49 +10:00
static history_mode_t * current_mode = 0 ;
2006-09-08 22:34:55 +10:00
2006-10-22 11:21:02 +10:00
/**
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 ) ;
}
/**
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 ;
2011-12-26 19:18:46 -08:00
return ( i1 - > timestamp = = i2 - > timestamp ) & & ( wcscmp ( i1 - > data , i2 - > data ) = = 0 ) ;
2006-10-22 11:21:02 +10:00
}
2006-10-08 23:50:46 +10:00
/**
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 ' ) ;
}
2011-12-26 19:18:46 -08:00
2006-10-08 23:50:46 +10:00
}
else if ( * in = = L ' \n ' )
{
sb_append_char ( out , L ' \\ ' ) ;
sb_append_char ( out , * in ) ;
}
else
{
sb_append_char ( out , * in ) ;
}
2011-12-26 19:18:46 -08:00
2006-10-08 23:50:46 +10:00
}
return ( wchar_t * ) out - > buff ;
}
2012-02-05 20:54:41 -08:00
static void replace_all ( wcstring & str , const wchar_t * needle , const wchar_t * replacement )
2012-02-05 16:42:24 -08:00
{
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 ;
}
}
2012-02-05 20:54:41 -08:00
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_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 ' ) ;
}
2006-10-08 23:50:46 +10:00
/**
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 ;
}
2008-01-14 02:47:47 +10:00
/**
Check if the specified item is already loaded
*/
2006-10-21 08:36:49 +10:00
static int item_is_new ( history_mode_t * m , void * d )
2011-12-26 19:18:46 -08:00
{
2006-10-21 08:36:49 +10:00
char * begin = ( char * ) d ;
2006-10-20 01:36:03 +10:00
2006-10-21 08:36:49 +10:00
if ( ! m - > has_loaded | | ! m - > mmap_start | | ( m - > mmap_start = = MAP_FAILED ) )
{
2011-12-26 19:18:46 -08:00
return 1 ;
2006-10-21 08:36:49 +10:00
}
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
if ( ( begin < m - > mmap_start ) | | ( begin > ( m - > mmap_start + m - > mmap_length ) ) )
2006-10-08 23:50:46 +10:00
{
2006-10-21 08:36:49 +10:00
return 1 ;
2006-10-08 23:50:46 +10:00
}
2006-10-21 08:36:49 +10:00
return 0 ;
}
2006-10-21 09:04:20 +10:00
/**
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 .
*/
2006-10-21 08:36:49 +10:00
static item_t * item_get ( history_mode_t * m , void * d )
2011-12-26 19:18:46 -08:00
{
2006-10-21 08:36:49 +10:00
char * begin = ( char * ) d ;
if ( item_is_new ( m , d ) )
2006-10-08 23:50:46 +10:00
{
2006-10-21 08:36:49 +10:00
return ( item_t * ) d ;
2006-10-08 23:50:46 +10:00
}
2006-10-21 08:36:49 +10:00
else
2006-10-08 23:50:46 +10:00
{
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
char * end = m - > mmap_start + m - > mmap_length ;
char * pos = begin ;
2011-12-26 19:18:46 -08:00
int was_backslash = 0 ;
2006-10-21 08:36:49 +10:00
static string_buffer_t * out = 0 ;
static item_t narrow_item ;
2011-12-26 19:18:46 -08:00
int first_char = 1 ;
2006-10-21 08:36:49 +10:00
int timestamp_mode = 0 ;
narrow_item . timestamp = 0 ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
if ( ! out )
2006-10-08 23:50:46 +10:00
{
2006-10-21 08:36:49 +10:00
out = sb_halloc ( global_context ) ;
if ( ! out )
2006-10-08 23:50:46 +10:00
{
2006-10-21 08:36:49 +10:00
DIE_MEM ( ) ;
2006-10-08 23:50:46 +10:00
}
}
2006-10-21 08:36:49 +10:00
else
2006-10-08 23:50:46 +10:00
{
2006-10-21 08:36:49 +10:00
sb_clear ( out ) ;
2006-10-08 23:50:46 +10:00
}
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
while ( 1 )
2005-09-20 23:26:39 +10:00
{
2006-10-21 08:36:49 +10:00
wchar_t c ;
mbstate_t state ;
size_t res ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
memset ( & state , 0 , sizeof ( state ) ) ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
res = mbrtowc ( & c , pos , end - pos , & state ) ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
if ( res = = ( size_t ) - 1 )
2005-09-20 23:26:39 +10:00
{
2006-10-21 08:36:49 +10:00
pos + + ;
continue ;
}
else if ( res = = ( size_t ) - 2 )
{
break ;
}
else if ( res = = ( size_t ) 0 )
{
pos + + ;
continue ;
}
pos + = res ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
if ( c = = L ' \n ' )
{
if ( timestamp_mode )
2005-09-20 23:26:39 +10:00
{
2006-10-21 08:36:49 +10:00
wchar_t * time_string = ( wchar_t * ) out - > buff ;
while ( * time_string & & ! iswdigit ( * time_string ) )
time_string + + ;
errno = 0 ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
if ( * time_string )
{
2007-01-09 13:20:05 +10:00
time_t tm ;
wchar_t * end ;
2011-12-26 19:18:46 -08:00
2007-01-09 13:20:05 +10:00
errno = 0 ;
tm = ( time_t ) wcstol ( time_string , & end , 10 ) ;
2011-12-26 19:18:46 -08:00
2007-01-09 13:20:05 +10:00
if ( tm & & ! errno & & ! * end )
2006-10-21 08:36:49 +10:00
{
narrow_item . timestamp = tm ;
}
2011-12-26 19:18:46 -08:00
}
2006-10-21 08:36:49 +10:00
sb_clear ( out ) ;
timestamp_mode = 0 ;
continue ;
2005-09-20 23:26:39 +10:00
}
2006-10-21 08:36:49 +10:00
if ( ! was_backslash )
break ;
}
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
if ( first_char )
{
2011-12-26 19:18:46 -08:00
if ( c = = L ' # ' )
2006-10-21 08:36:49 +10:00
timestamp_mode = 1 ;
2005-09-20 23:26:39 +10:00
}
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
first_char = 0 ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
sb_append_char ( out , c ) ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
was_backslash = ( ( c = = L ' \\ ' ) & & ! was_backslash ) ;
2011-12-26 19:18:46 -08:00
2005-09-20 23:26:39 +10:00
}
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
narrow_item . data = history_unescape_newlines ( ( wchar_t * ) out - > buff ) ;
return & narrow_item ;
2006-10-19 21:50:23 +10:00
}
2005-09-20 23:26:39 +10:00
}
2006-10-21 09:04:20 +10:00
/**
Write the specified item to the specified file .
*/
2007-01-13 02:17:24 +10:00
static int item_write ( FILE * f , history_mode_t * m , void * v )
2005-09-20 23:26:39 +10:00
{
2006-10-21 08:36:49 +10:00
item_t * i = item_get ( m , v ) ;
2007-01-13 02:17:24 +10:00
return fwprintf ( f , L " # %d \n %ls \n " , i - > timestamp , history_escape_newlines ( i - > data ) ) ;
2005-09-20 23:26:39 +10:00
}
2006-10-21 09:04:20 +10:00
/**
Release all memory used by the specified history mode .
*/
2006-10-21 20:30:35 +10:00
static void history_destroy_mode ( history_mode_t * m )
2005-09-20 23:26:39 +10:00
{
2006-10-21 08:36:49 +10:00
halloc_free ( m - > item_context ) ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
if ( m - > mmap_start & & ( m - > mmap_start ! = MAP_FAILED ) )
{
munmap ( m - > mmap_start , m - > mmap_length ) ;
2011-12-26 19:18:46 -08:00
}
2006-10-21 20:30:35 +10:00
}
2008-01-14 02:47:47 +10:00
/**
Free all memory used by specified mistory mode
*/
2006-10-21 20:30:35 +10:00
static void history_destroy_mode_wrapper ( void * n , history_mode_t * m )
{
2006-10-21 08:36:49 +10:00
halloc_free ( m ) ;
2005-09-20 23:26:39 +10:00
}
2006-10-21 09:04:20 +10:00
/**
2011-12-26 19:18:46 -08:00
Create a new empty mode with the specified name .
2006-10-21 20:30:35 +10:00
The mode is a halloc context , and the entire mode can be destroyed using halloc_free ( ) .
2006-10-21 09:04:20 +10:00
*/
2006-10-21 08:36:49 +10:00
static history_mode_t * history_create_mode ( const wchar_t * name )
2011-12-26 19:18:46 -08:00
{
history_mode_t * new_mode = ( history_mode_t * ) halloc ( 0 , sizeof ( history_mode_t ) ) ;
2006-10-21 20:16:09 +10:00
2006-10-21 08:36:49 +10:00
new_mode - > name = intern ( name ) ;
2006-10-21 20:16:09 +10:00
2006-10-21 08:36:49 +10:00
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 ) ;
2011-12-26 19:18:46 -08:00
2006-10-22 11:21:02 +10:00
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 ) ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
new_mode - > save_timestamp = time ( 0 ) ;
new_mode - > item_context = halloc ( 0 , 0 ) ;
2006-10-21 20:30:35 +10:00
halloc_register_function ( new_mode , ( void ( * ) ( void * ) ) & history_destroy_mode , new_mode ) ;
2011-12-26 19:18:46 -08:00
return new_mode ;
2005-09-20 23:26:39 +10:00
}
/**
2006-10-21 08:36:49 +10:00
This function tests if the search string is a match for the given string
2005-09-20 23:26:39 +10:00
*/
2006-10-21 08:36:49 +10:00
static int history_test ( const wchar_t * needle , const wchar_t * haystack )
2005-09-20 23:26:39 +10:00
{
2006-10-21 08:36:49 +10:00
return ! ! wcsstr ( haystack , needle ) ;
2005-09-20 23:26:39 +10:00
}
2006-10-21 09:04:20 +10:00
/**
Returns the name of the save file for a mode .
2006-10-21 08:36:49 +10:00
2006-10-21 09:04:20 +10:00
\ param context a halloc context used to allocate memory
\ param name the name of the hstory mode
\ param suffix an optional file suffix
*/
2006-10-21 08:36:49 +10:00
static wchar_t * history_filename ( void * context , const wchar_t * name , const wchar_t * suffix )
2005-09-20 23:26:39 +10:00
{
2006-10-21 08:36:49 +10:00
wchar_t * path ;
2011-12-26 19:18:46 -08:00
wchar_t * res ;
2005-09-20 23:26:39 +10:00
2006-10-21 08:36:49 +10:00
if ( ! current_mode )
return 0 ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
path = path_get_config ( context ) ;
2005-09-20 23:26:39 +10:00
2006-10-21 08:36:49 +10:00
if ( ! path )
return 0 ;
2011-12-26 19:18:46 -08:00
res = wcsdupcat ( path , L " / " , name , L " _history " , suffix ? suffix : NULL ) ;
2006-10-21 08:36:49 +10:00
halloc_register_function ( context , & free , res ) ;
return res ;
}
2005-09-20 23:26:39 +10:00
2012-02-05 16:42:24 -08:00
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 ;
}
2006-10-21 09:04:20 +10:00
/**
Go through the mmaped region and insert pointers to suitable loacations into the item list
*/
2006-10-21 08:36:49 +10:00
static void history_populate_from_mmap ( history_mode_t * m )
2011-12-26 19:18:46 -08:00
{
2006-10-21 08:36:49 +10:00
char * begin = m - > mmap_start ;
char * end = begin + m - > mmap_length ;
char * pos ;
2011-12-26 19:18:46 -08:00
2006-10-22 11:21:02 +10:00
array_list_t old_item ;
array_list_t session_item_list ;
2006-10-21 08:36:49 +10:00
int ignore_newline = 0 ;
int do_push = 1 ;
2011-12-26 19:18:46 -08:00
2006-10-22 11:21:02 +10:00
al_init ( & old_item ) ;
al_init ( & session_item_list ) ;
al_push_all ( & old_item , & m - > item ) ;
2006-10-21 08:36:49 +10:00
al_truncate ( & m - > item , 0 ) ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
for ( pos = begin ; pos < end ; pos + + )
2006-06-22 10:12:28 +10:00
{
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
if ( do_push )
2005-09-20 23:26:39 +10:00
{
2006-10-22 11:21:02 +10:00
item_t * i ;
item_t * i_orig ;
2006-10-21 08:36:49 +10:00
ignore_newline = * pos = = ' # ' ;
2006-10-22 11:21:02 +10:00
i = item_get ( m , pos ) ;
2011-12-26 19:18:46 -08:00
if ( ( i_orig = ( item_t * ) hash_get ( & current_mode - > session_item , i ) ) )
2006-10-22 11:21:02 +10:00
{
2006-10-26 06:46:50 +10:00
/*
2006-11-11 20:54:52 +10:00
This item comes from this session . Insert the
original item at the end of the item list .
2006-10-26 06:46:50 +10:00
*/
2011-12-26 19:18:46 -08:00
al_push ( & session_item_list , i_orig ) ;
2006-10-22 11:21:02 +10:00
}
else
{
2006-10-26 06:46:50 +10:00
/*
Old item . Insert pointer directly to the item list
*/
2006-10-22 11:21:02 +10:00
al_push ( & m - > item , pos ) ;
}
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
do_push = 0 ;
2005-09-20 23:26:39 +10:00
}
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
switch ( * pos )
2005-09-20 23:26:39 +10:00
{
2006-10-21 08:36:49 +10:00
case ' \\ ' :
2006-09-08 22:34:55 +10:00
{
2006-10-21 08:36:49 +10:00
pos + + ;
break ;
}
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
case ' \n ' :
{
if ( ignore_newline )
2006-10-19 21:50:23 +10:00
{
2006-10-21 08:36:49 +10:00
ignore_newline = 0 ;
2006-10-19 21:50:23 +10:00
}
else
2006-09-08 22:34:55 +10:00
{
2006-10-21 08:36:49 +10:00
do_push = 1 ;
2011-12-26 19:18:46 -08:00
}
2006-10-21 08:36:49 +10:00
break ;
2006-09-08 22:34:55 +10:00
}
2005-09-20 23:26:39 +10:00
}
2011-12-26 19:18:46 -08:00
}
2006-10-22 11:21:02 +10:00
al_push_all ( & m - > item , & session_item_list ) ;
2006-10-21 08:36:49 +10:00
m - > pos + = al_get_count ( & m - > item ) ;
2006-10-22 11:21:02 +10:00
al_push_all ( & m - > item , & old_item ) ;
al_destroy ( & session_item_list ) ;
al_destroy ( & old_item ) ;
2005-09-20 23:26:39 +10:00
}
2006-10-21 09:04:20 +10:00
/**
Load contents of the backing file to memory
*/
2006-10-21 08:36:49 +10:00
static void history_load ( history_mode_t * m )
2005-09-20 23:26:39 +10:00
{
2006-10-21 08:36:49 +10:00
int fd ;
int ok = 0 ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
void * context ;
wchar_t * filename ;
if ( ! m )
2011-12-26 19:18:46 -08:00
return ;
2006-10-22 11:21:02 +10:00
m - > has_loaded = 1 ;
2006-10-21 09:04:20 +10:00
signal_block ( ) ;
2006-10-21 08:36:49 +10:00
context = halloc ( 0 , 0 ) ;
filename = history_filename ( context , m - > name , 0 ) ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
if ( filename )
2005-09-20 23:26:39 +10:00
{
2006-10-21 08:36:49 +10:00
if ( ( fd = wopen ( filename , O_RDONLY ) ) > 0 )
2005-09-20 23:26:39 +10:00
{
2006-10-21 08:36:49 +10:00
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 )
{
2011-12-26 19:18:46 -08:00
if ( ( m - > mmap_start = ( char * ) mmap ( 0 , m - > mmap_length , PROT_READ , MAP_PRIVATE , fd , 0 ) ) ! = MAP_FAILED )
2006-10-21 08:36:49 +10:00
{
ok = 1 ;
history_populate_from_mmap ( m ) ;
}
}
}
close ( fd ) ;
}
2005-09-20 23:26:39 +10:00
}
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
halloc_free ( context ) ;
2006-10-21 09:04:20 +10:00
signal_unblock ( ) ;
2005-09-20 23:26:39 +10:00
}
2012-02-05 20:54:41 -08:00
/** Save the specified mode to file */
void history_t : : save_internal ( )
2012-02-05 16:42:24 -08:00
{
2012-02-05 20:54:41 -08:00
/* This must be called while locked */
2012-02-05 16:42:24 -08:00
/* Nothing to do if there's no new items */
if ( new_items . empty ( ) )
return ;
2012-02-05 20:54:41 -08:00
bool ok = true ;
2012-02-05 16:42:24 -08:00
signal_block ( ) ;
wcstring tmp_name = history_filename ( name , L " .tmp " ) ;
if ( ! tmp_name . empty ( ) )
{
2012-02-05 20:54:41 -08:00
FILE * out ;
if ( ( out = wfopen ( tmp_name . c_str ( ) , " w " ) ) )
2012-02-05 16:42:24 -08:00
{
2012-02-05 20:54:41 -08:00
/* Load old */
load_old_if_needed ( ) ;
/* Make an LRU cache to save only the last N elements */
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 + + ) {
size_t offset = * iter ;
history_item_t item = history_t : : decode_item ( mmap_start + offset , mmap_length - offset ) ;
lru . add_item ( item ) ;
}
/* Insert new items */
for ( std : : vector < history_item_t > : : iterator iter = new_items . begin ( ) ; iter ! = new_items . end ( ) ; iter + + ) {
lru . add_item ( * iter ) ;
}
/* 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
}
2012-02-05 16:42:24 -08:00
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
{
2012-02-05 20:54:41 -08:00
wcstring new_name = history_filename ( name , wcstring ( ) ) ;
wrename ( tmp_name . c_str ( ) , new_name . c_str ( ) ) ;
2012-02-05 16:42:24 -08:00
}
}
}
if ( ok )
{
2012-02-05 20:54:41 -08:00
/* 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 ) ;
2012-02-05 16:42:24 -08:00
}
signal_unblock ( ) ;
}
2012-02-05 20:54:41 -08:00
void history_t : : save ( void ) {
scoped_lock locker ( lock ) ;
this - > save_internal ( ) ;
}
2012-02-05 16:42:24 -08:00
2006-10-21 09:04:20 +10:00
/**
Save the specified mode to file
*/
2006-10-21 08:36:49 +10:00
static void history_save_mode ( void * n , history_mode_t * m )
2005-09-20 23:26:39 +10:00
{
2006-10-21 08:36:49 +10:00
FILE * out ;
history_mode_t * on_disk ;
int i ;
2007-01-17 01:11:30 +10:00
int has_new = 0 ;
2006-10-21 08:36:49 +10:00
wchar_t * tmp_name ;
2007-01-20 12:33:47 +10:00
int ok = 1 ;
2006-10-21 08:36:49 +10:00
/*
2006-11-11 20:54:52 +10:00
First check if there are any new entries to save . If not , then
we can just return
2006-10-21 08:36:49 +10:00
*/
for ( i = 0 ; i < al_get_count ( & m - > item ) ; i + + )
2006-06-22 10:12:28 +10:00
{
2006-10-21 08:36:49 +10:00
void * ptr = al_get ( & m - > item , i ) ;
has_new = item_is_new ( m , ptr ) ;
if ( has_new )
2006-06-22 10:12:28 +10:00
{
2006-10-21 08:36:49 +10:00
break ;
2006-06-22 10:12:28 +10:00
}
}
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
if ( ! has_new )
2005-09-20 23:26:39 +10:00
{
2006-10-21 08:36:49 +10:00
return ;
2005-09-20 23:26:39 +10:00
}
2011-12-26 19:18:46 -08:00
2006-10-21 09:04:20 +10:00
signal_block ( ) ;
2006-10-21 08:36:49 +10:00
/*
2006-11-11 20:54:52 +10:00
Set up on_disk variable to describe the current contents of the
history file
2006-10-21 08:36:49 +10:00
*/
on_disk = history_create_mode ( m - > name ) ;
history_load ( on_disk ) ;
2011-12-26 19:18:46 -08:00
2006-10-21 20:16:09 +10:00
tmp_name = history_filename ( on_disk , m - > name , L " .tmp " ) ;
2005-09-20 23:26:39 +10:00
2006-10-21 08:36:49 +10:00
if ( tmp_name )
2006-06-22 10:12:28 +10:00
{
2006-10-21 08:36:49 +10:00
tmp_name = wcsdup ( tmp_name ) ;
2011-12-26 19:18:46 -08:00
2006-10-21 20:16:09 +10:00
if ( ( out = wfopen ( tmp_name , " w " ) ) )
2006-06-22 10:12:28 +10:00
{
2006-10-21 08:36:49 +10:00
hash_table_t mine ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
hash_init ( & mine , & hash_item_func , & hash_item_cmp ) ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
for ( i = 0 ; i < al_get_count ( & m - > item ) ; i + + )
{
2006-10-21 09:04:20 +10:00
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 " " ) ;
}
}
2011-12-26 19:18:46 -08:00
2006-10-21 09:04:20 +10:00
/*
Re - save the old history
*/
2007-01-13 02:17:24 +10:00
for ( i = 0 ; ok & & ( i < al_get_count ( & on_disk - > item ) ) ; i + + )
2006-10-21 08:36:49 +10:00
{
2006-10-21 09:04:20 +10:00
void * ptr = al_get ( & on_disk - > item , i ) ;
item_t * i = item_get ( on_disk , ptr ) ;
if ( ! hash_get ( & mine , i ) )
2007-01-13 02:17:24 +10:00
{
if ( item_write ( out , on_disk , ptr ) = = - 1 )
{
ok = 0 ;
break ;
}
}
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
}
2011-12-26 19:18:46 -08:00
2006-10-21 09:04:20 +10:00
hash_destroy ( & mine ) ;
2011-12-26 19:18:46 -08:00
2006-10-21 09:04:20 +10:00
/*
2006-10-26 06:46:50 +10:00
Add our own items last
2011-12-26 19:18:46 -08:00
*/
2007-01-13 02:17:24 +10:00
for ( i = 0 ; ok & & ( i < al_get_count ( & m - > item ) ) ; i + + )
2006-10-21 09:04:20 +10:00
{
void * ptr = al_get ( & m - > item , i ) ;
int is_new = item_is_new ( m , ptr ) ;
if ( is_new )
2007-01-13 02:17:24 +10:00
{
if ( item_write ( out , m , ptr ) = = - 1 )
2011-12-26 19:18:46 -08:00
{
2007-01-13 02:17:24 +10:00
ok = 0 ;
}
}
2006-10-21 09:04:20 +10:00
}
2011-12-26 19:18:46 -08:00
2007-01-13 02:17:24 +10:00
if ( fclose ( out ) | | ! ok )
2006-10-21 09:04:20 +10:00
{
2006-10-26 06:46:50 +10:00
/*
This message does not have high enough priority to
be shown by default .
*/
debug ( 2 , L " Error when writing history file " ) ;
2006-10-21 09:04:20 +10:00
}
else
{
2006-10-21 20:16:09 +10:00
wrename ( tmp_name , history_filename ( on_disk , m - > name , 0 ) ) ;
2006-10-21 09:04:20 +10:00
}
2006-10-21 08:36:49 +10:00
}
free ( tmp_name ) ;
2011-12-26 19:18:46 -08:00
}
2006-10-21 20:30:35 +10:00
halloc_free ( on_disk ) ;
2006-10-22 11:21:02 +10:00
2007-01-20 12:33:47 +10:00
if ( ok )
{
2006-10-21 08:36:49 +10:00
2007-01-20 12:33:47 +10:00
/*
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 .
*/
2011-12-26 19:18:46 -08:00
2007-01-20 12:33:47 +10:00
if ( m - > mmap_start & & ( m - > mmap_start ! = MAP_FAILED ) )
{
munmap ( m - > mmap_start , m - > mmap_length ) ;
}
2011-12-26 19:18:46 -08:00
2007-01-20 12:33:47 +10:00
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 ;
2011-12-26 19:18:46 -08:00
2007-01-20 12:33:47 +10:00
m - > save_timestamp = time ( 0 ) ;
m - > new_count = 0 ;
}
2011-12-26 19:18:46 -08:00
2006-10-21 09:04:20 +10:00
signal_unblock ( ) ;
2006-10-21 08:36:49 +10:00
}
void history_add ( const wchar_t * str )
{
item_t * i ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
if ( ! current_mode )
return ;
2011-12-26 19:18:46 -08:00
i = ( item_t * ) halloc ( current_mode - > item_context , sizeof ( item_t ) ) ;
2006-10-21 08:36:49 +10:00
i - > data = ( wchar_t * ) halloc_wcsdup ( current_mode - > item_context , str ) ;
i - > timestamp = time ( 0 ) ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
al_push ( & current_mode - > item , i ) ;
2006-10-22 11:21:02 +10:00
hash_put ( & current_mode - > session_item , i , i ) ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
al_truncate ( & current_mode - > used , 0 ) ;
current_mode - > pos = al_get_count ( & current_mode - > item ) ;
current_mode - > new_count + + ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
if ( ( time ( 0 ) > current_mode - > save_timestamp + SAVE_INTERVAL ) | | ( current_mode - > new_count > = SAVE_COUNT ) )
2005-09-20 23:26:39 +10:00
{
2006-10-21 08:36:49 +10:00
history_save_mode ( 0 , current_mode ) ;
2005-09-20 23:26:39 +10:00
}
2011-12-26 19:18:46 -08:00
2005-09-20 23:26:39 +10:00
}
/**
2006-10-21 08:36:49 +10:00
Check if the specified string has already been used a s a search match
2005-09-20 23:26:39 +10:00
*/
2006-10-21 08:36:49 +10:00
static int history_is_used ( const wchar_t * str )
2005-09-20 23:26:39 +10:00
{
2006-10-21 08:36:49 +10:00
int i ;
int res = 0 ;
item_t * it = 0 ;
2005-09-20 23:26:39 +10:00
2006-10-21 08:36:49 +10:00
for ( i = 0 ; i < al_get_count ( & current_mode - > used ) ; i + + )
2006-06-22 10:12:28 +10:00
{
2006-10-21 08:36:49 +10:00
long idx = al_get_long ( & current_mode - > used , i ) ;
it = item_get ( current_mode , al_get ( & current_mode - > item , ( int ) idx ) ) ;
if ( wcscmp ( it - > data , str ) = = 0 )
2006-06-22 10:12:28 +10:00
{
2006-10-21 08:36:49 +10:00
res = 1 ;
2011-12-26 19:18:46 -08:00
break ;
2006-06-22 10:12:28 +10:00
}
2011-12-26 19:18:46 -08:00
2006-06-22 10:12:28 +10:00
}
2006-10-21 08:36:49 +10:00
return res ;
2005-09-20 23:26:39 +10:00
}
2006-10-21 08:36:49 +10:00
const wchar_t * history_prev_match ( const wchar_t * needle )
2005-09-20 23:26:39 +10:00
{
2006-10-21 08:36:49 +10:00
if ( current_mode )
2006-06-22 10:12:28 +10:00
{
2006-10-21 08:36:49 +10:00
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 ( & current_mode - > item , current_mode - > pos ) ) ;
wchar_t * haystack = ( wchar_t * ) i - > data ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
if ( history_test ( needle , haystack ) )
{
int is_used ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
/*
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
2006-10-22 11:21:02 +10:00
item_get ( ) function . Therefore , we must create
2006-10-21 08:36:49 +10:00
a copy of the haystack string , and if the string
is unused , we must call item_get anew .
*/
haystack = wcsdup ( haystack ) ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
is_used = history_is_used ( haystack ) ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
free ( haystack ) ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
if ( ! is_used )
{
i = item_get ( current_mode , al_get ( & current_mode - > item , current_mode - > pos ) ) ;
al_push_long ( & current_mode - > used , current_mode - > pos ) ;
return i - > data ;
}
}
}
}
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
if ( ! current_mode - > has_loaded )
{
2006-10-26 06:46:50 +10:00
/*
We found no match in the list , try loading the history
file and continue the search
*/
2006-10-21 08:36:49 +10:00
history_load ( current_mode ) ;
return history_prev_match ( needle ) ;
}
else
2006-06-22 10:12:28 +10:00
{
2006-10-26 06:46:50 +10:00
/*
We found no match in the list , and the file is already
loaded . Set poition before first element and return
original search string .
*/
2006-10-21 08:36:49 +10:00
current_mode - > pos = - 1 ;
if ( al_peek_long ( & current_mode - > used ) ! = - 1 )
al_push_long ( & current_mode - > used , - 1 ) ;
2006-06-22 10:12:28 +10:00
}
2011-12-26 19:18:46 -08:00
2006-06-22 10:12:28 +10:00
}
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
return needle ;
}
wchar_t * history_get ( int idx )
{
int len ;
2006-02-07 01:08:10 +10:00
2006-10-21 08:36:49 +10:00
if ( ! current_mode )
return 0 ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
len = al_get_count ( & current_mode - > item ) ;
2005-09-20 23:26:39 +10:00
2006-10-21 08:36:49 +10:00
if ( ( idx > = len ) & & ! current_mode - > has_loaded )
2005-09-20 23:26:39 +10:00
{
2006-10-21 08:36:49 +10:00
history_load ( current_mode ) ;
len = al_get_count ( & current_mode - > item ) ;
2005-09-20 23:26:39 +10:00
}
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
if ( idx < 0 )
return 0 ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
if ( idx > = len )
return 0 ;
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
return item_get ( current_mode , al_get ( & current_mode - > item , len - 1 - idx ) ) - > data ;
2005-09-20 23:26:39 +10:00
}
2006-10-21 08:36:49 +10:00
void history_first ( )
2005-09-20 23:26:39 +10:00
{
2006-10-21 08:36:49 +10:00
if ( current_mode )
{
if ( ! current_mode - > has_loaded )
{
history_load ( current_mode ) ;
}
2011-12-26 19:18:46 -08:00
2006-10-21 08:36:49 +10:00
current_mode - > pos = 0 ;
2011-12-26 19:18:46 -08:00
}
2005-09-20 23:26:39 +10:00
}
2006-10-21 08:36:49 +10:00
void history_reset ( )
2005-09-20 23:26:39 +10:00
{
2006-10-21 08:36:49 +10:00
if ( current_mode )
{
current_mode - > pos = al_get_count ( & current_mode - > item ) ;
2006-10-22 11:21:02 +10:00
/*
Clear list of search matches
*/
2006-10-21 08:36:49 +10:00
al_truncate ( & current_mode - > used , 0 ) ;
2011-12-26 19:18:46 -08:00
}
2005-09-20 23:26:39 +10:00
}
2006-10-21 08:36:49 +10:00
const wchar_t * history_next_match ( const wchar_t * needle )
2005-09-20 23:26:39 +10:00
{
2006-10-21 08:36:49 +10:00
if ( current_mode )
2006-06-22 10:12:28 +10:00
{
2006-10-22 11:21:02 +10:00
/*
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 !
*/
2006-10-21 08:36:49 +10:00
if ( al_get_count ( & current_mode - > used ) )
2006-06-22 10:12:28 +10:00
{
2006-10-21 08:36:49 +10:00
al_pop ( & current_mode - > used ) ;
if ( al_get_count ( & current_mode - > used ) )
{
current_mode - > pos = ( int ) al_peek_long ( & current_mode - > used ) ;
item_t * i = item_get ( current_mode , al_get ( & current_mode - > item , current_mode - > pos ) ) ;
return i - > data ;
}
2006-06-22 10:12:28 +10:00
}
2011-12-26 19:18:46 -08:00
2006-10-22 11:21:02 +10:00
/*
The used - list is empty . Set position to ' past end of list '
and return the search string .
*/
2006-10-21 08:36:49 +10:00
current_mode - > pos = al_get_count ( & current_mode - > item ) ;
2006-06-22 10:12:28 +10:00
}
2011-12-26 19:18:46 -08:00
return needle ;
2006-10-21 08:36:49 +10:00
}
void history_init ( )
2006-10-21 20:30:35 +10:00
{
2006-10-21 08:36:49 +10:00
}
2005-09-20 23:26:39 +10:00
2006-10-21 08:36:49 +10:00
void history_destroy ( )
{
2012-02-05 20:54:41 -08:00
/* Save all histories */
for ( std : : map < wcstring , history_t * > : : iterator iter = histories . begin ( ) ; iter ! = histories . end ( ) ; iter + + ) {
iter - > second - > save ( ) ;
}
2006-10-21 08:36:49 +10:00
}
2005-09-20 23:26:39 +10:00
2006-10-21 08:36:49 +10:00
void history_sanity_check ( )
{
2006-10-22 11:21:02 +10:00
/*
No sanity checking implemented yet . . .
*/
2005-09-20 23:26:39 +10:00
}