1 #include <algorithm>
2 #include <memory>
3 #include <fstream>
4 #include <cstring>
5 
6 #ifndef _WIN32
7 
8 #include <unistd.h>
9 #include <fcntl.h>
10 #include <sys/stat.h>
11 
12 #endif /* _WIN32 */
13 
14 #include "replxx.hxx"
15 #include "history.hxx"
16 
17 using namespace std;
18 
19 namespace replxx {
20 
21 namespace {
delete_ReplxxHistoryScanImpl(Replxx::HistoryScanImpl * impl_)22 void delete_ReplxxHistoryScanImpl( Replxx::HistoryScanImpl* impl_ ) {
23 	delete impl_;
24 }
25 }
26 
27 static int const REPLXX_DEFAULT_HISTORY_MAX_LEN( 1000 );
28 
HistoryScan(impl_t impl_)29 Replxx::HistoryScan::HistoryScan( impl_t impl_ )
30 	: _impl( std::move( impl_ ) ) {
31 }
32 
next(void)33 bool Replxx::HistoryScan::next( void ) {
34 	return ( _impl->next() );
35 }
36 
HistoryScanImpl(History::entries_t const & entries_)37 Replxx::HistoryScanImpl::HistoryScanImpl( History::entries_t const& entries_ )
38 	: _entries( entries_ )
39 	, _it( _entries.end() )
40 	, _utf8Cache()
41 	, _entryCache( std::string(), std::string() )
42 	, _cacheValid( false ) {
43 }
44 
get(void) const45 Replxx::HistoryEntry const& Replxx::HistoryScan::get( void ) const {
46 	return ( _impl->get() );
47 }
48 
next(void)49 bool Replxx::HistoryScanImpl::next( void ) {
50 	if ( _it == _entries.end() ) {
51 		_it = _entries.begin();
52 	} else {
53 		++ _it;
54 	}
55 	_cacheValid = false;
56 	return ( _it != _entries.end() );
57 }
58 
get(void) const59 Replxx::HistoryEntry const& Replxx::HistoryScanImpl::get( void ) const {
60 	if ( _cacheValid ) {
61 		return ( _entryCache );
62 	}
63 	_utf8Cache.assign( _it->text() );
64 	_entryCache = Replxx::HistoryEntry( _it->timestamp(), _utf8Cache.get() );
65 	_cacheValid = true;
66 	return ( _entryCache );
67 }
68 
scan(void) const69 Replxx::HistoryScan::impl_t History::scan( void ) const {
70 	return ( Replxx::HistoryScan::impl_t( new Replxx::HistoryScanImpl( _entries ), delete_ReplxxHistoryScanImpl ) );
71 }
72 
History(void)73 History::History( void )
74 	: _entries()
75 	, _maxSize( REPLXX_DEFAULT_HISTORY_MAX_LEN )
76 	, _current( _entries.begin() )
77 	, _yankPos( _entries.end() )
78 	, _previous( _entries.begin() )
79 	, _recallMostRecent( false )
80 	, _unique( true ) {
81 }
82 
add(UnicodeString const & line,std::string const & when)83 void History::add( UnicodeString const& line, std::string const& when ) {
84 	if ( _maxSize <= 0 ) {
85 		return;
86 	}
87 	if ( ! _entries.empty() && ( line == _entries.back().text() ) ) {
88 		_entries.back() = Entry( now_ms_str(), line );
89 		return;
90 	}
91 	remove_duplicate( line );
92 	trim_to_max_size();
93 	_entries.emplace_back( when, line );
94 	_locations.insert( make_pair( line, last() ) );
95 	if ( _current == _entries.end() ) {
96 		_current = last();
97 	}
98 	_yankPos = _entries.end();
99 }
100 
101 #ifndef _WIN32
102 class FileLock {
103 	std::string _path;
104 	int _lockFd;
105 public:
FileLock(std::string const & name_)106 	FileLock( std::string const& name_ )
107 		: _path( name_ + ".lock" )
108 		, _lockFd( ::open( _path.c_str(), O_CREAT | O_RDWR, 0600 ) ) {
109 		static_cast<void>( ::lockf( _lockFd, F_LOCK, 0 ) == 0 );
110 	}
~FileLock(void)111 	~FileLock( void ) {
112 		static_cast<void>( ::lockf( _lockFd, F_ULOCK, 0 ) == 0 );
113 		::close( _lockFd );
114 		::unlink( _path.c_str() );
115 		return;
116 	}
117 };
118 #endif
119 
save(std::string const & filename,bool sync_)120 bool History::save( std::string const& filename, bool sync_ ) {
121 #ifndef _WIN32
122 	mode_t old_umask = umask( S_IXUSR | S_IRWXG | S_IRWXO );
123 	FileLock fileLock( filename );
124 #endif
125 	entries_t entries;
126 	locations_t locations;
127 	if ( ! sync_ ) {
128 		entries.swap( _entries );
129 		locations.swap( _locations );
130 		_entries = entries;
131 		reset_iters();
132 	}
133 	do_load( filename );
134 	sort();
135 	remove_duplicates();
136 	trim_to_max_size();
137 	ofstream histFile( filename );
138 	if ( ! histFile ) {
139 		return ( false );
140 	}
141 #ifndef _WIN32
142 	umask( old_umask );
143 	chmod( filename.c_str(), S_IRUSR | S_IWUSR );
144 #endif
145 	Utf8String utf8;
146 	for ( Entry const& h : _entries ) {
147 		if ( ! h.text().is_empty() ) {
148 			utf8.assign( h.text() );
149 			histFile << "### " << h.timestamp() << "\n" << utf8.get() << endl;
150 		}
151 	}
152 	if ( ! sync_ ) {
153 		_entries = std::move( entries );
154 		_locations = std::move( locations );
155 	}
156 	reset_iters();
157 	return ( true );
158 }
159 
160 namespace {
161 
is_timestamp(std::string const & s)162 bool is_timestamp( std::string const& s ) {
163 	static char const TIMESTAMP_PATTERN[] = "### dddd-dd-dd dd:dd:dd.ddd";
164 	static int const TIMESTAMP_LENGTH( sizeof ( TIMESTAMP_PATTERN ) - 1 );
165 	if ( s.length() != TIMESTAMP_LENGTH ) {
166 		return ( false );
167 	}
168 	for ( int i( 0 ); i < TIMESTAMP_LENGTH; ++ i ) {
169 		if ( TIMESTAMP_PATTERN[i] == 'd' ) {
170 			if ( ! isdigit( s[i] ) ) {
171 				return ( false );
172 			}
173 		} else if ( s[i] != TIMESTAMP_PATTERN[i] ) {
174 			return ( false );
175 		}
176 	}
177 	return ( true );
178 }
179 
180 }
181 
do_load(std::string const & filename)182 bool History::do_load( std::string const& filename ) {
183 	ifstream histFile( filename );
184 	if ( ! histFile ) {
185 		return ( false );
186 	}
187 	string line;
188 	string when( "0000-00-00 00:00:00.000" );
189 	while ( getline( histFile, line ).good() ) {
190 		string::size_type eol( line.find_first_of( "\r\n" ) );
191 		if ( eol != string::npos ) {
192 			line.erase( eol );
193 		}
194 		if ( is_timestamp( line ) ) {
195 			when.assign( line, 4, std::string::npos );
196 			continue;
197 		}
198 		if ( ! line.empty() ) {
199 			_entries.emplace_back( when, UnicodeString( line ) );
200 		}
201 	}
202 	return ( true );
203 }
204 
load(std::string const & filename)205 bool History::load( std::string const& filename ) {
206 	clear();
207 	bool success( do_load( filename ) );
208 	sort();
209 	remove_duplicates();
210 	trim_to_max_size();
211 	_previous = _current = last();
212 	_yankPos = _entries.end();
213 	return ( success );
214 }
215 
sort(void)216 void History::sort( void ) {
217 	typedef std::vector<Entry> sortable_entries_t;
218 	_locations.clear();
219 	sortable_entries_t sortableEntries( _entries.begin(), _entries.end() );
220 	std::stable_sort( sortableEntries.begin(), sortableEntries.end() );
221 	_entries.clear();
222 	_entries.insert( _entries.begin(), sortableEntries.begin(), sortableEntries.end() );
223 }
224 
clear(void)225 void History::clear( void ) {
226 	_locations.clear();
227 	_entries.clear();
228 	_current = _entries.begin();
229 	_recallMostRecent = false;
230 }
231 
set_max_size(int size_)232 void History::set_max_size( int size_ ) {
233 	if ( size_ >= 0 ) {
234 		_maxSize = size_;
235 		trim_to_max_size();
236 	}
237 }
238 
reset_yank_iterator(void)239 void History::reset_yank_iterator( void ) {
240 	_yankPos = _entries.end();
241 }
242 
next_yank_position(void)243 bool History::next_yank_position( void ) {
244 	bool resetYankSize( false );
245 	if ( _yankPos == _entries.end() ) {
246 		resetYankSize = true;
247 	}
248 	if ( ( _yankPos != _entries.begin() ) && ( _yankPos != _entries.end() ) ) {
249 		-- _yankPos;
250 	} else {
251 		_yankPos = moved( _entries.end(), -2 );
252 	}
253 	return ( resetYankSize );
254 }
255 
move(bool up_)256 bool History::move( bool up_ ) {
257 	bool doRecall( _recallMostRecent && ! up_ );
258 	if ( doRecall ) {
259 		_current = _previous; // emulate Windows down-arrow
260 	}
261 	_recallMostRecent = false;
262 	return ( doRecall || move( _current, up_ ? -1 : 1 ) );
263 }
264 
jump(bool start_,bool reset_)265 void History::jump( bool start_, bool reset_ ) {
266 	if ( start_ ) {
267 		_current = _entries.begin();
268 	} else {
269 		_current = last();
270 	}
271 	if ( reset_ ) {
272 		_recallMostRecent = false;
273 	}
274 }
275 
save_pos(void)276 void History::save_pos( void ) {
277 	_previous = _current;
278 }
279 
restore_pos(void)280 void History::restore_pos( void ) {
281 	_current = _previous;
282 }
283 
common_prefix_search(UnicodeString const & prefix_,int prefixSize_,bool back_)284 bool History::common_prefix_search( UnicodeString const& prefix_, int prefixSize_, bool back_ ) {
285 	int step( back_ ? -1 : 1 );
286 	entries_t::const_iterator it( moved( _current, step, true ) );
287 	while ( it != _current ) {
288 		if ( it->text().starts_with( prefix_.begin(), prefix_.begin() + prefixSize_ ) ) {
289 			_current = it;
290 			commit_index();
291 			return ( true );
292 		}
293 		move( it, step, true );
294 	}
295 	return ( false );
296 }
297 
move(entries_t::const_iterator & it_,int by_,bool wrapped_) const298 bool History::move( entries_t::const_iterator& it_, int by_, bool wrapped_ ) const {
299 	if ( by_ > 0 ) {
300 		for ( int i( 0 ); i < by_; ++ i ) {
301 			++ it_;
302 			if ( it_ != _entries.end() ) {
303 			} else if ( wrapped_ ) {
304 				it_ = _entries.begin();
305 			} else {
306 				-- it_;
307 				return ( false );
308 			}
309 		}
310 	} else {
311 		for ( int i( 0 ); i > by_; -- i ) {
312 			if ( it_ != _entries.begin() ) {
313 				-- it_;
314 			} else if ( wrapped_ ) {
315 				it_ = last();
316 			} else {
317 				return ( false );
318 			}
319 		}
320 	}
321 	return ( true );
322 }
323 
moved(entries_t::const_iterator it_,int by_,bool wrapped_) const324 History::entries_t::const_iterator History::moved( entries_t::const_iterator it_, int by_, bool wrapped_ ) const {
325 	move( it_, by_, wrapped_ );
326 	return ( it_ );
327 }
328 
erase(entries_t::const_iterator it_)329 void History::erase( entries_t::const_iterator it_ ) {
330 	bool invalidated( it_ == _current );
331 	_locations.erase( it_->text() );
332 	it_ = _entries.erase( it_ );
333 	if ( invalidated ) {
334 		_current = it_;
335 	}
336 	if ( ( _current == _entries.end() ) && ! _entries.empty() ) {
337 		-- _current;
338 	}
339 	_yankPos = _entries.end();
340 	_previous = _current;
341 }
342 
trim_to_max_size(void)343 void History::trim_to_max_size( void ) {
344 	while ( size() > _maxSize ) {
345 		erase( _entries.begin() );
346 	}
347 }
348 
remove_duplicate(UnicodeString const & line_)349 void History::remove_duplicate( UnicodeString const& line_ ) {
350 	if ( ! _unique ) {
351 		return;
352 	}
353 	locations_t::iterator it( _locations.find( line_ ) );
354 	if ( it == _locations.end() ) {
355 		return;
356 	}
357 	erase( it->second );
358 }
359 
remove_duplicates(void)360 void History::remove_duplicates( void ) {
361 	if ( ! _unique ) {
362 		return;
363 	}
364 	_locations.clear();
365 	typedef std::pair<locations_t::iterator, bool> locations_insertion_result_t;
366 	for ( entries_t::iterator it( _entries.begin() ), end( _entries.end() ); it != end; ++ it ) {
367 		locations_insertion_result_t locationsInsertionResult( _locations.insert( make_pair( it->text(), it ) ) );
368 		if ( ! locationsInsertionResult.second ) {
369 			_entries.erase( locationsInsertionResult.first->second );
370 			locationsInsertionResult.first->second = it;
371 		}
372 	}
373 }
374 
update_last(UnicodeString const & line_)375 void History::update_last( UnicodeString const& line_ ) {
376 	if ( _unique ) {
377 		_locations.erase( _entries.back().text() );
378 		remove_duplicate( line_ );
379 		_locations.insert( make_pair( line_, last() ) );
380 	}
381 	_entries.back() = Entry( now_ms_str(), line_ );
382 }
383 
drop_last(void)384 void History::drop_last( void ) {
385 	erase( last() );
386 }
387 
is_last(void) const388 bool History::is_last( void ) const {
389 	return ( _current == last() );
390 }
391 
last(void) const392 History::entries_t::const_iterator History::last( void ) const {
393 	return ( moved( _entries.end(), -1 ) );
394 }
395 
reset_iters(void)396 void History::reset_iters( void ) {
397 	_previous = _current = last();
398 	_yankPos = _entries.end();
399 }
400 
401 }
402 
403