2
This file is part of Konsole, an X terminal.
3
Copyright (C) 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
5
Rewritten for QT4 by e_k <e_k at users.sourceforge.net>, Copyright (C)2008
7
This program is free software; you can redistribute it and/or modify
8
it under the terms of the GNU General Public License as published by
9
the Free Software Foundation; either version 2 of the License, or
10
(at your option) any later version.
12
This program is distributed in the hope that it will be useful,
13
but WITHOUT ANY WARRANTY; without even the implied warranty of
14
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
15
GNU General Public License for more details.
17
You should have received a copy of the GNU General Public License
18
along with this program; if not, write to the Free Software
19
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
31
#include <sys/types.h>
37
// Reasonable line size
38
#define LINE_SIZE 1024
40
using namespace Konsole;
43
An arbitrary long scroll.
45
One can modify the scroll only by adding either cells
46
or newlines, but access it randomly.
48
The model is that of an arbitrary wide typewriter scroll
49
in that the scroll is a serie of lines and each line is
50
a serie of cells with no overwriting permitted.
52
The implementation provides arbitrary length and numbers
53
of cells and line/column indexed read access to the scroll
56
KDE4: Can we use QTemporaryFile here, instead of KTempFile?
58
FIXME: some complain about the history buffer comsuming the
59
memory of their machines. This problem is critical
60
since the history does not behave gracefully in cases
61
where the memory is used up completely.
63
I put in a workaround that should handle it problem
64
now gracefully. I'm not satisfied with the solution.
66
FIXME: Terminating the history is not properly indicated
67
in the menu. We should throw a signal.
69
FIXME: There is noticeable decrease in speed, also. Perhaps,
70
there whole feature needs to be revisited therefore.
71
Disadvantage of a more elaborated, say block-oriented
72
scheme with wrap around would be it's complexity.
75
//FIXME: tempory replacement for tmpfile
76
// this is here one for debugging purpose.
78
//#define tmpfile xTmpFile
80
// History File ///////////////////////////////////////////
83
A Row(X) data type which allows adding elements to the end.
86
HistoryFile::HistoryFile()
93
tmpFile.setAutoRemove( true );
94
ion = tmpFile.handle();
98
HistoryFile::~HistoryFile()
104
//TODO: Mapping the entire file in will cause problems if the history file becomes exceedingly large,
105
//(ie. larger than available memory). HistoryFile::map() should only map in sections of the file at a time,
107
void HistoryFile::map()
109
assert( fileMap == 0 );
111
fileMap = ( char* )mmap( 0 , length , PROT_READ , MAP_PRIVATE , ion , 0 );
113
//if mmap'ing fails, fall back to the read-lseek combination
114
if ( fileMap == MAP_FAILED )
116
readWriteBalance = 0;
118
qDebug() << ": mmap'ing history failed. errno = " << errno;
122
void HistoryFile::unmap()
124
int result = munmap( fileMap , length );
125
assert( result == 0 );
130
bool HistoryFile::isMapped()
132
return ( fileMap != 0 );
135
void HistoryFile::add( const unsigned char* bytes, int len )
144
rc = lseek( ion, length, SEEK_SET ); if ( rc < 0 ) { perror( "HistoryFile::add.seek" ); return; }
145
rc = write( ion, bytes, len ); if ( rc < 0 ) { perror( "HistoryFile::add.write" ); return; }
149
void HistoryFile::get( unsigned char* bytes, int len, int loc )
151
//count number of get() calls vs. number of add() calls.
152
//If there are many more get() calls compared with add()
153
//calls (decided by using MAP_THRESHOLD) then mmap the log
154
//file to improve performance.
156
if ( !fileMap && readWriteBalance < MAP_THRESHOLD )
161
for ( int i = 0; i < len; i++ )
162
bytes[i] = fileMap[loc+i];
168
if ( loc < 0 || len < 0 || loc + len > length )
169
fprintf( stderr, "getHist(...,%d,%d): invalid args.\n", len, loc );
170
rc = lseek( ion, loc, SEEK_SET ); if ( rc < 0 ) { perror( "HistoryFile::get.seek" ); return; }
171
rc = read( ion, bytes, len ); if ( rc < 0 ) { perror( "HistoryFile::get.read" ); return; }
175
int HistoryFile::len()
181
// History Scroll abstract base class //////////////////////////////////////
184
HistoryScroll::HistoryScroll( HistoryType* t )
189
HistoryScroll::~HistoryScroll()
194
bool HistoryScroll::hasScroll()
199
// History Scroll File //////////////////////////////////////
202
The history scroll makes a Row(Row(Cell)) from
203
two history buffers. The index buffer contains
204
start of line positions which refere to the cells
207
Note that index[0] addresses the second line
208
(line #1), while the first line (line #0) starts
212
HistoryScrollFile::HistoryScrollFile( const QString &logFileName )
213
: HistoryScroll( new HistoryTypeFile( logFileName ) ),
214
m_logFileName( logFileName )
218
HistoryScrollFile::~HistoryScrollFile()
222
int HistoryScrollFile::getLines()
224
return index.len() / sizeof( int );
227
int HistoryScrollFile::getLineLen( int lineno )
229
return ( startOfLine( lineno + 1 ) - startOfLine( lineno ) ) / sizeof( Character );
232
bool HistoryScrollFile::isWrappedLine( int lineno )
234
if ( lineno >= 0 && lineno <= getLines() )
237
lineflags.get(( unsigned char* )&flag, sizeof( unsigned char ), ( lineno )*sizeof( unsigned char ) );
243
int HistoryScrollFile::startOfLine( int lineno )
245
if ( lineno <= 0 ) return 0;
246
if ( lineno <= getLines() )
249
if ( !index.isMapped() )
253
index.get(( unsigned char* )&res, sizeof( int ), ( lineno - 1 )*sizeof( int ) );
259
void HistoryScrollFile::getCells( int lineno, int colno, int count, Character res[] )
261
cells.get(( unsigned char* )res, count*sizeof( Character ), startOfLine( lineno ) + colno*sizeof( Character ) );
264
void HistoryScrollFile::addCells( const Character text[], int count )
266
cells.add(( unsigned char* )text, count*sizeof( Character ) );
269
void HistoryScrollFile::addLine( bool previousWrapped )
271
if ( index.isMapped() )
274
int locn = cells.len();
275
index.add(( unsigned char* )&locn, sizeof( int ) );
276
unsigned char flags = previousWrapped ? 0x01 : 0x00;
277
lineflags.add(( unsigned char* )&flags, sizeof( unsigned char ) );
281
// History Scroll Buffer //////////////////////////////////////
282
HistoryScrollBuffer::HistoryScrollBuffer( unsigned int maxLineCount )
283
: HistoryScroll( new HistoryTypeBuffer( maxLineCount ) )
289
setMaxNbLines( maxLineCount );
292
HistoryScrollBuffer::~HistoryScrollBuffer()
294
delete[] _historyBuffer;
297
void HistoryScrollBuffer::addCellsVector( const QVector<Character>& cells )
300
if ( _usedLines < _maxLineCount )
303
if ( _head >= _maxLineCount )
308
_historyBuffer[bufferIndex( _usedLines-1 )] = cells;
309
_wrappedLine[bufferIndex( _usedLines-1 )] = false;
311
void HistoryScrollBuffer::addCells( const Character a[], int count )
313
HistoryLine newLine( count );
314
qCopy( a, a + count, newLine.begin() );
316
addCellsVector( newLine );
319
void HistoryScrollBuffer::addLine( bool previousWrapped )
321
_wrappedLine[bufferIndex( _usedLines-1 )] = previousWrapped;
324
int HistoryScrollBuffer::getLines()
329
int HistoryScrollBuffer::getLineLen( int lineNumber )
331
Q_ASSERT( lineNumber >= 0 && lineNumber < _maxLineCount );
333
if ( lineNumber < _usedLines )
335
return _historyBuffer[bufferIndex( lineNumber )].size();
343
bool HistoryScrollBuffer::isWrappedLine( int lineNumber )
345
Q_ASSERT( lineNumber >= 0 && lineNumber < _maxLineCount );
347
if ( lineNumber < _usedLines )
349
//kDebug() << "Line" << lineNumber << "wrapped is" << _wrappedLine[bufferIndex(lineNumber)];
350
return _wrappedLine[bufferIndex( lineNumber )];
356
void HistoryScrollBuffer::getCells( int lineNumber, int startColumn, int count, Character* buffer )
358
if ( count == 0 ) return;
360
Q_ASSERT( lineNumber < _maxLineCount );
362
if ( lineNumber >= _usedLines )
364
memset( buffer, 0, count * sizeof( Character ) );
368
const HistoryLine& line = _historyBuffer[bufferIndex( lineNumber )];
370
//kDebug() << "startCol " << startColumn;
371
//kDebug() << "line.size() " << line.size();
372
//kDebug() << "count " << count;
374
Q_ASSERT( startColumn <= line.size() - count );
376
memcpy( buffer, line.constData() + startColumn , count * sizeof( Character ) );
379
void HistoryScrollBuffer::setMaxNbLines( unsigned int lineCount )
381
HistoryLine* oldBuffer = _historyBuffer;
382
HistoryLine* newBuffer = new HistoryLine[lineCount];
384
for ( int i = 0 ; i < qMin( _usedLines, ( int )lineCount ) ; i++ )
386
newBuffer[i] = oldBuffer[bufferIndex( i )];
389
_usedLines = qMin( _usedLines, ( int )lineCount );
390
_maxLineCount = lineCount;
391
_head = ( _usedLines == _maxLineCount ) ? 0 : _usedLines - 1;
393
_historyBuffer = newBuffer;
396
_wrappedLine.resize( lineCount );
399
int HistoryScrollBuffer::bufferIndex( int lineNumber )
401
Q_ASSERT( lineNumber >= 0 );
402
Q_ASSERT( lineNumber < _maxLineCount );
403
Q_ASSERT(( _usedLines == _maxLineCount ) || lineNumber <= _head );
405
if ( _usedLines == _maxLineCount )
407
return ( _head + lineNumber + 1 ) % _maxLineCount;
416
// History Scroll None //////////////////////////////////////
418
HistoryScrollNone::HistoryScrollNone()
419
: HistoryScroll( new HistoryTypeNone() )
423
HistoryScrollNone::~HistoryScrollNone()
427
bool HistoryScrollNone::hasScroll()
432
int HistoryScrollNone::getLines()
437
int HistoryScrollNone::getLineLen( int )
442
bool HistoryScrollNone::isWrappedLine( int /*lineno*/ )
447
void HistoryScrollNone::getCells( int, int, int, Character [] )
451
void HistoryScrollNone::addCells( const Character [], int )
455
void HistoryScrollNone::addLine( bool )
459
// History Scroll BlockArray //////////////////////////////////////
461
HistoryScrollBlockArray::HistoryScrollBlockArray( size_t size )
462
: HistoryScroll( new HistoryTypeBlockArray( size ) )
464
m_blockArray.setHistorySize( size ); // nb. of lines.
467
HistoryScrollBlockArray::~HistoryScrollBlockArray()
471
int HistoryScrollBlockArray::getLines()
473
return m_lineLengths.count();
476
int HistoryScrollBlockArray::getLineLen( int lineno )
478
if ( m_lineLengths.contains( lineno ) )
479
return m_lineLengths[lineno];
484
bool HistoryScrollBlockArray::isWrappedLine( int /*lineno*/ )
489
void HistoryScrollBlockArray::getCells( int lineno, int colno,
490
int count, Character res[] )
492
if ( !count ) return;
494
const Block *b = m_blockArray.at( lineno );
498
memset( res, 0, count * sizeof( Character ) ); // still better than random data
502
assert((( colno + count ) * sizeof( Character ) ) < ENTRIES );
503
memcpy( res, b->data + ( colno * sizeof( Character ) ), count * sizeof( Character ) );
506
void HistoryScrollBlockArray::addCells( const Character a[], int count )
508
Block *b = m_blockArray.lastBlock();
512
// put cells in block's data
513
assert(( count * sizeof( Character ) ) < ENTRIES );
515
memset( b->data, 0, ENTRIES );
517
memcpy( b->data, a, count * sizeof( Character ) );
518
b->size = count * sizeof( Character );
520
size_t res = m_blockArray.newBlock();
524
m_lineLengths.insert( m_blockArray.getCurrent(), count );
527
void HistoryScrollBlockArray::addLine( bool )
531
//////////////////////////////////////////////////////////////////////
533
//////////////////////////////////////////////////////////////////////
535
HistoryType::HistoryType()
539
HistoryType::~HistoryType()
543
//////////////////////////////
545
HistoryTypeNone::HistoryTypeNone()
549
bool HistoryTypeNone::isEnabled() const
554
HistoryScroll* HistoryTypeNone::scroll( HistoryScroll *old ) const
557
return new HistoryScrollNone();
560
int HistoryTypeNone::maximumLineCount() const
565
//////////////////////////////
567
HistoryTypeBlockArray::HistoryTypeBlockArray( size_t size )
572
bool HistoryTypeBlockArray::isEnabled() const
577
int HistoryTypeBlockArray::maximumLineCount() const
582
HistoryScroll* HistoryTypeBlockArray::scroll( HistoryScroll *old ) const
585
return new HistoryScrollBlockArray( m_size );
589
//////////////////////////////
591
HistoryTypeBuffer::HistoryTypeBuffer( unsigned int nbLines )
592
: m_nbLines( nbLines )
596
bool HistoryTypeBuffer::isEnabled() const
601
int HistoryTypeBuffer::maximumLineCount() const
606
HistoryScroll* HistoryTypeBuffer::scroll( HistoryScroll *old ) const
610
HistoryScrollBuffer *oldBuffer = dynamic_cast<HistoryScrollBuffer*>( old );
613
oldBuffer->setMaxNbLines( m_nbLines );
617
HistoryScroll *newScroll = new HistoryScrollBuffer( m_nbLines );
618
int lines = old->getLines();
620
if ( lines > ( int ) m_nbLines )
621
startLine = lines - m_nbLines;
623
Character line[LINE_SIZE];
624
for ( int i = startLine; i < lines; i++ )
626
int size = old->getLineLen( i );
627
if ( size > LINE_SIZE )
629
Character *tmp_line = new Character[size];
630
old->getCells( i, 0, size, tmp_line );
631
newScroll->addCells( tmp_line, size );
632
newScroll->addLine( old->isWrappedLine( i ) );
637
old->getCells( i, 0, size, line );
638
newScroll->addCells( line, size );
639
newScroll->addLine( old->isWrappedLine( i ) );
645
return new HistoryScrollBuffer( m_nbLines );
648
//////////////////////////////
650
HistoryTypeFile::HistoryTypeFile( const QString& fileName )
651
: m_fileName( fileName )
655
bool HistoryTypeFile::isEnabled() const
660
const QString& HistoryTypeFile::getFileName() const
665
HistoryScroll* HistoryTypeFile::scroll( HistoryScroll *old ) const
667
if ( dynamic_cast<HistoryFile *>( old ) )
668
return old; // Unchanged.
670
HistoryScroll *newScroll = new HistoryScrollFile( m_fileName );
672
Character line[LINE_SIZE];
673
int lines = ( old != 0 ) ? old->getLines() : 0;
674
for ( int i = 0; i < lines; i++ )
676
int size = old->getLineLen( i );
677
if ( size > LINE_SIZE )
679
Character *tmp_line = new Character[size];
680
old->getCells( i, 0, size, tmp_line );
681
newScroll->addCells( tmp_line, size );
682
newScroll->addLine( old->isWrappedLine( i ) );
687
old->getCells( i, 0, size, line );
688
newScroll->addCells( line, size );
689
newScroll->addLine( old->isWrappedLine( i ) );
697
int HistoryTypeFile::maximumLineCount() const