summaryrefslogtreecommitdiffstats
path: root/contrib/replxx/src/history.cxx
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--contrib/replxx/src/history.cxx402
1 files changed, 402 insertions, 0 deletions
diff --git a/contrib/replxx/src/history.cxx b/contrib/replxx/src/history.cxx
new file mode 100644
index 0000000..fe691df
--- /dev/null
+++ b/contrib/replxx/src/history.cxx
@@ -0,0 +1,402 @@
+#include <algorithm>
+#include <memory>
+#include <fstream>
+#include <cstring>
+
+#ifndef _WIN32
+
+#include <unistd.h>
+#include <fcntl.h>
+#include <sys/stat.h>
+
+#endif /* _WIN32 */
+
+#include "replxx.hxx"
+#include "history.hxx"
+
+using namespace std;
+
+namespace replxx {
+
+namespace {
+void delete_ReplxxHistoryScanImpl( Replxx::HistoryScanImpl* impl_ ) {
+ delete impl_;
+}
+}
+
+static int const REPLXX_DEFAULT_HISTORY_MAX_LEN( 1000 );
+
+Replxx::HistoryScan::HistoryScan( impl_t impl_ )
+ : _impl( std::move( impl_ ) ) {
+}
+
+bool Replxx::HistoryScan::next( void ) {
+ return ( _impl->next() );
+}
+
+Replxx::HistoryScanImpl::HistoryScanImpl( History::entries_t const& entries_ )
+ : _entries( entries_ )
+ , _it( _entries.end() )
+ , _utf8Cache()
+ , _entryCache( std::string(), std::string() )
+ , _cacheValid( false ) {
+}
+
+Replxx::HistoryEntry const& Replxx::HistoryScan::get( void ) const {
+ return ( _impl->get() );
+}
+
+bool Replxx::HistoryScanImpl::next( void ) {
+ if ( _it == _entries.end() ) {
+ _it = _entries.begin();
+ } else {
+ ++ _it;
+ }
+ _cacheValid = false;
+ return ( _it != _entries.end() );
+}
+
+Replxx::HistoryEntry const& Replxx::HistoryScanImpl::get( void ) const {
+ if ( _cacheValid ) {
+ return ( _entryCache );
+ }
+ _utf8Cache.assign( _it->text() );
+ _entryCache = Replxx::HistoryEntry( _it->timestamp(), _utf8Cache.get() );
+ _cacheValid = true;
+ return ( _entryCache );
+}
+
+Replxx::HistoryScan::impl_t History::scan( void ) const {
+ return ( Replxx::HistoryScan::impl_t( new Replxx::HistoryScanImpl( _entries ), delete_ReplxxHistoryScanImpl ) );
+}
+
+History::History( void )
+ : _entries()
+ , _maxSize( REPLXX_DEFAULT_HISTORY_MAX_LEN )
+ , _current( _entries.begin() )
+ , _yankPos( _entries.end() )
+ , _previous( _entries.begin() )
+ , _recallMostRecent( false )
+ , _unique( true ) {
+}
+
+void History::add( UnicodeString const& line, std::string const& when ) {
+ if ( _maxSize <= 0 ) {
+ return;
+ }
+ if ( ! _entries.empty() && ( line == _entries.back().text() ) ) {
+ _entries.back() = Entry( now_ms_str(), line );
+ return;
+ }
+ remove_duplicate( line );
+ trim_to_max_size();
+ _entries.emplace_back( when, line );
+ _locations.insert( make_pair( line, last() ) );
+ if ( _current == _entries.end() ) {
+ _current = last();
+ }
+ _yankPos = _entries.end();
+}
+
+#ifndef _WIN32
+class FileLock {
+ std::string _path;
+ int _lockFd;
+public:
+ FileLock( std::string const& name_ )
+ : _path( name_ + ".lock" )
+ , _lockFd( ::open( _path.c_str(), O_CREAT | O_RDWR, 0600 ) ) {
+ static_cast<void>( ::lockf( _lockFd, F_LOCK, 0 ) == 0 );
+ }
+ ~FileLock( void ) {
+ static_cast<void>( ::lockf( _lockFd, F_ULOCK, 0 ) == 0 );
+ ::close( _lockFd );
+ ::unlink( _path.c_str() );
+ return;
+ }
+};
+#endif
+
+bool History::save( std::string const& filename, bool sync_ ) {
+#ifndef _WIN32
+ mode_t old_umask = umask( S_IXUSR | S_IRWXG | S_IRWXO );
+ FileLock fileLock( filename );
+#endif
+ entries_t entries;
+ locations_t locations;
+ if ( ! sync_ ) {
+ entries.swap( _entries );
+ locations.swap( _locations );
+ _entries = entries;
+ reset_iters();
+ }
+ do_load( filename );
+ sort();
+ remove_duplicates();
+ trim_to_max_size();
+ ofstream histFile( filename );
+ if ( ! histFile ) {
+ return ( false );
+ }
+#ifndef _WIN32
+ umask( old_umask );
+ chmod( filename.c_str(), S_IRUSR | S_IWUSR );
+#endif
+ Utf8String utf8;
+ for ( Entry const& h : _entries ) {
+ if ( ! h.text().is_empty() ) {
+ utf8.assign( h.text() );
+ histFile << "### " << h.timestamp() << "\n" << utf8.get() << endl;
+ }
+ }
+ if ( ! sync_ ) {
+ _entries = std::move( entries );
+ _locations = std::move( locations );
+ }
+ reset_iters();
+ return ( true );
+}
+
+namespace {
+
+bool is_timestamp( std::string const& s ) {
+ static char const TIMESTAMP_PATTERN[] = "### dddd-dd-dd dd:dd:dd.ddd";
+ static int const TIMESTAMP_LENGTH( sizeof ( TIMESTAMP_PATTERN ) - 1 );
+ if ( s.length() != TIMESTAMP_LENGTH ) {
+ return ( false );
+ }
+ for ( int i( 0 ); i < TIMESTAMP_LENGTH; ++ i ) {
+ if ( TIMESTAMP_PATTERN[i] == 'd' ) {
+ if ( ! isdigit( s[i] ) ) {
+ return ( false );
+ }
+ } else if ( s[i] != TIMESTAMP_PATTERN[i] ) {
+ return ( false );
+ }
+ }
+ return ( true );
+}
+
+}
+
+bool History::do_load( std::string const& filename ) {
+ ifstream histFile( filename );
+ if ( ! histFile ) {
+ return ( false );
+ }
+ string line;
+ string when( "0000-00-00 00:00:00.000" );
+ while ( getline( histFile, line ).good() ) {
+ string::size_type eol( line.find_first_of( "\r\n" ) );
+ if ( eol != string::npos ) {
+ line.erase( eol );
+ }
+ if ( is_timestamp( line ) ) {
+ when.assign( line, 4, std::string::npos );
+ continue;
+ }
+ if ( ! line.empty() ) {
+ _entries.emplace_back( when, UnicodeString( line ) );
+ }
+ }
+ return ( true );
+}
+
+bool History::load( std::string const& filename ) {
+ clear();
+ bool success( do_load( filename ) );
+ sort();
+ remove_duplicates();
+ trim_to_max_size();
+ _previous = _current = last();
+ _yankPos = _entries.end();
+ return ( success );
+}
+
+void History::sort( void ) {
+ typedef std::vector<Entry> sortable_entries_t;
+ _locations.clear();
+ sortable_entries_t sortableEntries( _entries.begin(), _entries.end() );
+ std::stable_sort( sortableEntries.begin(), sortableEntries.end() );
+ _entries.clear();
+ _entries.insert( _entries.begin(), sortableEntries.begin(), sortableEntries.end() );
+}
+
+void History::clear( void ) {
+ _locations.clear();
+ _entries.clear();
+ _current = _entries.begin();
+ _recallMostRecent = false;
+}
+
+void History::set_max_size( int size_ ) {
+ if ( size_ >= 0 ) {
+ _maxSize = size_;
+ trim_to_max_size();
+ }
+}
+
+void History::reset_yank_iterator( void ) {
+ _yankPos = _entries.end();
+}
+
+bool History::next_yank_position( void ) {
+ bool resetYankSize( false );
+ if ( _yankPos == _entries.end() ) {
+ resetYankSize = true;
+ }
+ if ( ( _yankPos != _entries.begin() ) && ( _yankPos != _entries.end() ) ) {
+ -- _yankPos;
+ } else {
+ _yankPos = moved( _entries.end(), -2 );
+ }
+ return ( resetYankSize );
+}
+
+bool History::move( bool up_ ) {
+ bool doRecall( _recallMostRecent && ! up_ );
+ if ( doRecall ) {
+ _current = _previous; // emulate Windows down-arrow
+ }
+ _recallMostRecent = false;
+ return ( doRecall || move( _current, up_ ? -1 : 1 ) );
+}
+
+void History::jump( bool start_, bool reset_ ) {
+ if ( start_ ) {
+ _current = _entries.begin();
+ } else {
+ _current = last();
+ }
+ if ( reset_ ) {
+ _recallMostRecent = false;
+ }
+}
+
+void History::save_pos( void ) {
+ _previous = _current;
+}
+
+void History::restore_pos( void ) {
+ _current = _previous;
+}
+
+bool History::common_prefix_search( UnicodeString const& prefix_, int prefixSize_, bool back_ ) {
+ int step( back_ ? -1 : 1 );
+ entries_t::const_iterator it( moved( _current, step, true ) );
+ while ( it != _current ) {
+ if ( it->text().starts_with( prefix_.begin(), prefix_.begin() + prefixSize_ ) ) {
+ _current = it;
+ commit_index();
+ return ( true );
+ }
+ move( it, step, true );
+ }
+ return ( false );
+}
+
+bool History::move( entries_t::const_iterator& it_, int by_, bool wrapped_ ) const {
+ if ( by_ > 0 ) {
+ for ( int i( 0 ); i < by_; ++ i ) {
+ ++ it_;
+ if ( it_ != _entries.end() ) {
+ } else if ( wrapped_ ) {
+ it_ = _entries.begin();
+ } else {
+ -- it_;
+ return ( false );
+ }
+ }
+ } else {
+ for ( int i( 0 ); i > by_; -- i ) {
+ if ( it_ != _entries.begin() ) {
+ -- it_;
+ } else if ( wrapped_ ) {
+ it_ = last();
+ } else {
+ return ( false );
+ }
+ }
+ }
+ return ( true );
+}
+
+History::entries_t::const_iterator History::moved( entries_t::const_iterator it_, int by_, bool wrapped_ ) const {
+ move( it_, by_, wrapped_ );
+ return ( it_ );
+}
+
+void History::erase( entries_t::const_iterator it_ ) {
+ bool invalidated( it_ == _current );
+ _locations.erase( it_->text() );
+ it_ = _entries.erase( it_ );
+ if ( invalidated ) {
+ _current = it_;
+ }
+ if ( ( _current == _entries.end() ) && ! _entries.empty() ) {
+ -- _current;
+ }
+ _yankPos = _entries.end();
+ _previous = _current;
+}
+
+void History::trim_to_max_size( void ) {
+ while ( size() > _maxSize ) {
+ erase( _entries.begin() );
+ }
+}
+
+void History::remove_duplicate( UnicodeString const& line_ ) {
+ if ( ! _unique ) {
+ return;
+ }
+ locations_t::iterator it( _locations.find( line_ ) );
+ if ( it == _locations.end() ) {
+ return;
+ }
+ erase( it->second );
+}
+
+void History::remove_duplicates( void ) {
+ if ( ! _unique ) {
+ return;
+ }
+ _locations.clear();
+ typedef std::pair<locations_t::iterator, bool> locations_insertion_result_t;
+ for ( entries_t::iterator it( _entries.begin() ), end( _entries.end() ); it != end; ++ it ) {
+ locations_insertion_result_t locationsInsertionResult( _locations.insert( make_pair( it->text(), it ) ) );
+ if ( ! locationsInsertionResult.second ) {
+ _entries.erase( locationsInsertionResult.first->second );
+ locationsInsertionResult.first->second = it;
+ }
+ }
+}
+
+void History::update_last( UnicodeString const& line_ ) {
+ if ( _unique ) {
+ _locations.erase( _entries.back().text() );
+ remove_duplicate( line_ );
+ _locations.insert( make_pair( line_, last() ) );
+ }
+ _entries.back() = Entry( now_ms_str(), line_ );
+}
+
+void History::drop_last( void ) {
+ erase( last() );
+}
+
+bool History::is_last( void ) const {
+ return ( _current == last() );
+}
+
+History::entries_t::const_iterator History::last( void ) const {
+ return ( moved( _entries.end(), -1 ) );
+}
+
+void History::reset_iters( void ) {
+ _previous = _current = last();
+ _yankPos = _entries.end();
+}
+
+}
+