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