@@ -457,12 +457,61 @@ private void ReadHistoryFile()
457457 {
458458 WithHistoryFileMutexDo ( 1000 , ( ) =>
459459 {
460- var historyLines = File . ReadAllLines ( Options . HistorySavePath ) ;
460+ var historyLines = ReadHistoryLinesImpl ( Options . HistorySavePath , Options . MaximumHistoryCount ) ;
461461 UpdateHistoryFromFile ( historyLines , fromDifferentSession : false , fromInitialRead : true ) ;
462462 var fileInfo = new FileInfo ( Options . HistorySavePath ) ;
463463 _historyFileLastSavedSize = fileInfo . Length ;
464464 } ) ;
465465 }
466+
467+ static IEnumerable < string > ReadHistoryLinesImpl ( string path , int historyCount )
468+ {
469+ const long offset_1mb = 1048576 ;
470+ const long offset_05mb = 524288 ;
471+
472+ // 1mb content contains more than 34,000 history lines for a typical usage, which should be
473+ // more than enough to cover 20,000 history records (a history record could be a multi-line
474+ // command). Similarly, 0.5mb content should be enough to cover 10,000 history records.
475+ // We optimize the file reading when the history count falls in those ranges. If the history
476+ // count is even larger, which should be very rare, we just read all lines.
477+ long offset = historyCount switch
478+ {
479+ <= 10000 => offset_05mb ,
480+ <= 20000 => offset_1mb ,
481+ _ => 0 ,
482+ } ;
483+
484+ using var fs = new FileStream ( path , FileMode . Open ) ;
485+ using var sr = new StreamReader ( fs ) ;
486+
487+ if ( offset > 0 && fs . Length > offset )
488+ {
489+ // When the file size is larger than the offset, we only read that amount of content from the end.
490+ fs . Seek ( - offset , SeekOrigin . End ) ;
491+
492+ // After seeking, the current position may point at the middle of a history record, or even at a
493+ // byte within a UTF-8 character (history file is saved with UTF-8 encoding). So, let's ignore the
494+ // first line read from that position.
495+ sr . ReadLine ( ) ;
496+
497+ string line ;
498+ while ( ( line = sr . ReadLine ( ) ) is not null )
499+ {
500+ if ( ! line . EndsWith ( "`" , StringComparison . Ordinal ) )
501+ {
502+ // A complete history record is guaranteed to start from the next line.
503+ break ;
504+ }
505+ }
506+ }
507+
508+ // Read lines in the streaming way, so it won't consume to much memory even if we have to
509+ // read all lines from a large history file.
510+ while ( ! sr . EndOfStream )
511+ {
512+ yield return sr . ReadLine ( ) ;
513+ }
514+ }
466515 }
467516
468517 void UpdateHistoryFromFile ( IEnumerable < string > historyLines , bool fromDifferentSession , bool fromInitialRead )
0 commit comments