2
This file is part of Konsole, an X terminal.
3
Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de>
5
This program is free software; you can redistribute it and/or modify
6
it under the terms of the GNU General Public License as published by
7
the Free Software Foundation; either version 2 of the License, or
8
(at your option) any later version.
10
This program is distributed in the hope that it will be useful,
11
but WITHOUT ANY WARRANTY; without even the implied warranty of
12
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13
GNU General Public License for more details.
15
You should have received a copy of the GNU General Public License
16
along with this program; if not, write to the Free Software
17
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
29
#include <sys/types.h>
37
//#include <kde_file.h>
40
// Reasonable line size
41
#define LINE_SIZE 1024
42
#define KDE_lseek lseek
44
using namespace Konsole;
47
An arbitrary long scroll.
49
One can modify the scroll only by adding either cells
50
or newlines, but access it randomly.
52
The model is that of an arbitrary wide typewriter scroll
53
in that the scroll is a serie of lines and each line is
54
a serie of cells with no overwriting permitted.
56
The implementation provides arbitrary length and numbers
57
of cells and line/column indexed read access to the scroll
60
KDE4: Can we use QTemporaryFile here, instead of KTempFile?
62
FIXME: some complain about the history buffer comsuming the
63
memory of their machines. This problem is critical
64
since the history does not behave gracefully in cases
65
where the memory is used up completely.
67
I put in a workaround that should handle it problem
68
now gracefully. I'm not satisfied with the solution.
70
FIXME: Terminating the history is not properly indicated
71
in the menu. We should throw a signal.
73
FIXME: There is noticeable decrease in speed, also. Perhaps,
74
there whole feature needs to be revisited therefore.
75
Disadvantage of a more elaborated, say block-oriented
76
scheme with wrap around would be it's complexity.
79
//FIXME: tempory replacement for tmpfile
80
// this is here one for debugging purpose.
82
//#define tmpfile xTmpFile
84
// History File ///////////////////////////////////////////
87
A Row(X) data type which allows adding elements to the end.
90
HistoryFile::HistoryFile()
97
tmpFile.setAutoRemove(true);
98
ion = tmpFile.handle();
102
HistoryFile::~HistoryFile()
108
//TODO: Mapping the entire file in will cause problems if the history file becomes exceedingly large,
109
//(ie. larger than available memory). HistoryFile::map() should only map in sections of the file at a time,
111
void HistoryFile::map()
113
assert( fileMap == 0 );
115
fileMap = (char*)mmap( 0 , length , PROT_READ , MAP_PRIVATE , ion , 0 );
117
//if mmap'ing fails, fall back to the read-lseek combination
118
if ( fileMap == MAP_FAILED )
120
readWriteBalance = 0;
122
qDebug() << __FILE__ << __LINE__ << ": mmap'ing history failed. errno = " << errno;
126
void HistoryFile::unmap()
128
int result = munmap( fileMap , length );
129
assert( result == 0 ); Q_UNUSED( result );
134
bool HistoryFile::isMapped()
136
return (fileMap != 0);
139
void HistoryFile::add(const unsigned char* bytes, int len)
148
rc = KDE_lseek(ion,length,SEEK_SET); if (rc < 0) { perror("HistoryFile::add.seek"); return; }
149
rc = write(ion,bytes,len); if (rc < 0) { perror("HistoryFile::add.write"); return; }
153
void HistoryFile::get(unsigned char* bytes, int len, int loc)
155
//count number of get() calls vs. number of add() calls.
156
//If there are many more get() calls compared with add()
157
//calls (decided by using MAP_THRESHOLD) then mmap the log
158
//file to improve performance.
160
if ( !fileMap && readWriteBalance < MAP_THRESHOLD )
165
for (int i=0;i<len;i++)
166
bytes[i]=fileMap[loc+i];
172
if (loc < 0 || len < 0 || loc + len > length)
173
fprintf(stderr,"getHist(...,%d,%d): invalid args.\n",len,loc);
174
rc = KDE_lseek(ion,loc,SEEK_SET); if (rc < 0) { perror("HistoryFile::get.seek"); return; }
175
rc = read(ion,bytes,len); if (rc < 0) { perror("HistoryFile::get.read"); return; }
179
int HistoryFile::len()
185
// History Scroll abstract base class //////////////////////////////////////
188
HistoryScroll::HistoryScroll(HistoryType* t)
193
HistoryScroll::~HistoryScroll()
198
bool HistoryScroll::hasScroll()
203
// History Scroll File //////////////////////////////////////
206
The history scroll makes a Row(Row(Cell)) from
207
two history buffers. The index buffer contains
208
start of line positions which refere to the cells
211
Note that index[0] addresses the second line
212
(line #1), while the first line (line #0) starts
216
HistoryScrollFile::HistoryScrollFile(const QString &logFileName)
217
: HistoryScroll(new HistoryTypeFile(logFileName)),
218
m_logFileName(logFileName)
222
HistoryScrollFile::~HistoryScrollFile()
226
int HistoryScrollFile::getLines()
228
return index.len() / sizeof(int);
231
int HistoryScrollFile::getLineLen(int lineno)
233
return (startOfLine(lineno+1) - startOfLine(lineno)) / sizeof(Character);
236
bool HistoryScrollFile::isWrappedLine(int lineno)
238
if (lineno>=0 && lineno <= getLines()) {
240
lineflags.get((unsigned char*)&flag,sizeof(unsigned char),(lineno)*sizeof(unsigned char));
246
int HistoryScrollFile::startOfLine(int lineno)
248
if (lineno <= 0) return 0;
249
if (lineno <= getLines())
252
if (!index.isMapped())
256
index.get((unsigned char*)&res,sizeof(int),(lineno-1)*sizeof(int));
262
void HistoryScrollFile::getCells(int lineno, int colno, int count, Character res[])
264
cells.get((unsigned char*)res,count*sizeof(Character),startOfLine(lineno)+colno*sizeof(Character));
267
void HistoryScrollFile::addCells(const Character text[], int count)
269
cells.add((unsigned char*)text,count*sizeof(Character));
272
void HistoryScrollFile::addLine(bool previousWrapped)
274
if (index.isMapped())
277
int locn = cells.len();
278
index.add((unsigned char*)&locn,sizeof(int));
279
unsigned char flags = previousWrapped ? 0x01 : 0x00;
280
lineflags.add((unsigned char*)&flags,sizeof(unsigned char));
284
// History Scroll Buffer //////////////////////////////////////
285
HistoryScrollBuffer::HistoryScrollBuffer(unsigned int maxLineCount)
286
: HistoryScroll(new HistoryTypeBuffer(maxLineCount))
292
setMaxNbLines(maxLineCount);
295
HistoryScrollBuffer::~HistoryScrollBuffer()
297
delete[] _historyBuffer;
300
void HistoryScrollBuffer::addCellsVector(const QVector<Character>& cells)
303
if ( _usedLines < _maxLineCount )
306
if ( _head >= _maxLineCount )
311
_historyBuffer[bufferIndex(_usedLines-1)] = cells;
312
_wrappedLine[bufferIndex(_usedLines-1)] = false;
314
void HistoryScrollBuffer::addCells(const Character a[], int count)
316
HistoryLine newLine(count);
317
qCopy(a,a+count,newLine.begin());
319
addCellsVector(newLine);
322
void HistoryScrollBuffer::addLine(bool previousWrapped)
324
_wrappedLine[bufferIndex(_usedLines-1)] = previousWrapped;
327
int HistoryScrollBuffer::getLines()
332
int HistoryScrollBuffer::getLineLen(int lineNumber)
334
Q_ASSERT( lineNumber >= 0 && lineNumber < _maxLineCount );
336
if ( lineNumber < _usedLines )
338
return _historyBuffer[bufferIndex(lineNumber)].size();
346
bool HistoryScrollBuffer::isWrappedLine(int lineNumber)
348
Q_ASSERT( lineNumber >= 0 && lineNumber < _maxLineCount );
350
if (lineNumber < _usedLines)
352
//kDebug() << "Line" << lineNumber << "wrapped is" << _wrappedLine[bufferIndex(lineNumber)];
353
return _wrappedLine[bufferIndex(lineNumber)];
359
void HistoryScrollBuffer::getCells(int lineNumber, int startColumn, int count, Character buffer[])
361
if ( count == 0 ) return;
363
Q_ASSERT( lineNumber < _maxLineCount );
365
if (lineNumber >= _usedLines)
367
memset(buffer, 0, count * sizeof(Character));
371
const HistoryLine& line = _historyBuffer[bufferIndex(lineNumber)];
373
//kDebug() << "startCol " << startColumn;
374
//kDebug() << "line.size() " << line.size();
375
//kDebug() << "count " << count;
377
Q_ASSERT( startColumn <= line.size() - count );
379
memcpy(buffer, line.constData() + startColumn , count * sizeof(Character));
382
void HistoryScrollBuffer::setMaxNbLines(unsigned int lineCount)
384
HistoryLine* oldBuffer = _historyBuffer;
385
HistoryLine* newBuffer = new HistoryLine[lineCount];
387
for ( int i = 0 ; i < qMin(_usedLines,(int)lineCount) ; i++ )
389
newBuffer[i] = oldBuffer[bufferIndex(i)];
392
_usedLines = qMin(_usedLines,(int)lineCount);
393
_maxLineCount = lineCount;
394
_head = ( _usedLines == _maxLineCount ) ? 0 : _usedLines-1;
396
_historyBuffer = newBuffer;
399
_wrappedLine.resize(lineCount);
400
dynamic_cast<HistoryTypeBuffer*>(m_histType)->m_nbLines = lineCount;
403
int HistoryScrollBuffer::bufferIndex(int lineNumber)
405
Q_ASSERT( lineNumber >= 0 );
406
Q_ASSERT( lineNumber < _maxLineCount );
407
Q_ASSERT( (_usedLines == _maxLineCount) || lineNumber <= _head );
409
if ( _usedLines == _maxLineCount )
411
return (_head+lineNumber+1) % _maxLineCount;
420
// History Scroll None //////////////////////////////////////
422
HistoryScrollNone::HistoryScrollNone()
423
: HistoryScroll(new HistoryTypeNone())
427
HistoryScrollNone::~HistoryScrollNone()
431
bool HistoryScrollNone::hasScroll()
436
int HistoryScrollNone::getLines()
441
int HistoryScrollNone::getLineLen(int)
446
bool HistoryScrollNone::isWrappedLine(int /*lineno*/)
451
void HistoryScrollNone::getCells(int, int, int, Character [])
455
void HistoryScrollNone::addCells(const Character [], int)
459
void HistoryScrollNone::addLine(bool)
463
// History Scroll BlockArray //////////////////////////////////////
465
HistoryScrollBlockArray::HistoryScrollBlockArray(size_t size)
466
: HistoryScroll(new HistoryTypeBlockArray(size))
468
m_blockArray.setHistorySize(size); // nb. of lines.
471
HistoryScrollBlockArray::~HistoryScrollBlockArray()
475
int HistoryScrollBlockArray::getLines()
477
return m_lineLengths.count();
480
int HistoryScrollBlockArray::getLineLen(int lineno)
482
if ( m_lineLengths.contains(lineno) )
483
return m_lineLengths[lineno];
488
bool HistoryScrollBlockArray::isWrappedLine(int /*lineno*/)
493
void HistoryScrollBlockArray::getCells(int lineno, int colno,
494
int count, Character res[])
498
const Block *b = m_blockArray.at(lineno);
501
memset(res, 0, count * sizeof(Character)); // still better than random data
505
assert(((colno + count) * sizeof(Character)) < ENTRIES);
506
memcpy(res, b->data + (colno * sizeof(Character)), count * sizeof(Character));
509
void HistoryScrollBlockArray::addCells(const Character a[], int count)
511
Block *b = m_blockArray.lastBlock();
515
// put cells in block's data
516
assert((count * sizeof(Character)) < ENTRIES);
518
memset(b->data, 0, ENTRIES);
520
memcpy(b->data, a, count * sizeof(Character));
521
b->size = count * sizeof(Character);
523
size_t res = m_blockArray.newBlock();
527
m_lineLengths.insert(m_blockArray.getCurrent(), count);
530
void HistoryScrollBlockArray::addLine(bool)
534
////////////////////////////////////////////////////////////////
535
// Compact History Scroll //////////////////////////////////////
536
////////////////////////////////////////////////////////////////
537
void* CompactHistoryBlock::allocate ( size_t length )
539
Q_ASSERT ( length > 0 );
540
if ( tail-blockStart+length > blockLength )
545
//kDebug() << "allocated " << length << " bytes at address " << block;
550
void CompactHistoryBlock::deallocate ( )
553
Q_ASSERT ( allocCount >= 0 );
556
void* CompactHistoryBlockList::allocate(size_t size)
558
CompactHistoryBlock* block;
559
if ( list.isEmpty() || list.last()->remaining() < size)
561
block = new CompactHistoryBlock();
562
list.append ( block );
563
//kDebug() << "new block created, remaining " << block->remaining() << "number of blocks=" << list.size();
568
//kDebug() << "old block used, remaining " << block->remaining();
570
return block->allocate(size);
573
void CompactHistoryBlockList::deallocate(void* ptr)
575
Q_ASSERT( !list.isEmpty());
578
CompactHistoryBlock *block = list.at(i);
579
while ( i<list.size() && !block->contains(ptr) )
585
Q_ASSERT( i<list.size() );
589
if (!block->isInUse())
593
//kDebug() << "block deleted, new size = " << list.size();
597
CompactHistoryBlockList::~CompactHistoryBlockList()
599
qDeleteAll ( list.begin(), list.end() );
603
void* CompactHistoryLine::operator new (size_t size, CompactHistoryBlockList& blockList)
605
return blockList.allocate(size);
608
CompactHistoryLine::CompactHistoryLine ( const TextLine& line, CompactHistoryBlockList& bList )
614
if (line.size() > 0) {
618
// count number of different formats in this text line
619
Character c = line[0];
622
if ( !(line[k].equalsFormat(c)))
624
formatLength++; // format change detected
630
//kDebug() << "number of different formats in string: " << formatLength;
631
formatArray = (CharacterFormat*) blockList.allocate(sizeof(CharacterFormat)*formatLength);
632
Q_ASSERT (formatArray!=NULL);
633
text = (quint16*) blockList.allocate(sizeof(quint16)*line.size());
634
Q_ASSERT (text!=NULL);
637
formatLength=formatLength;
640
// record formats and their positions in the format array
642
formatArray[0].setFormat ( c );
643
formatArray[0].startPos=0; // there's always at least 1 format (for the entire line, unless a change happens)
645
k=1; // look for possible format changes
647
while ( k<length && j<formatLength )
649
if (!(line[k].equalsFormat(c)))
652
formatArray[j].setFormat(c);
653
formatArray[j].startPos=k;
654
//kDebug() << "format entry " << j << " at pos " << formatArray[j].startPos << " " << &(formatArray[j].startPos) ;
660
// copy character values
661
for ( int i=0; i<line.size(); i++ )
663
text[i]=line[i].character;
664
//kDebug() << "char " << i << " at mem " << &(text[i]);
667
//kDebug() << "line created, length " << length << " at " << &(length);
670
CompactHistoryLine::~CompactHistoryLine()
672
//kDebug() << "~CHL";
674
blockList.deallocate(text);
675
blockList.deallocate(formatArray);
677
blockList.deallocate(this);
680
void CompactHistoryLine::getCharacter ( int index, Character &r )
682
Q_ASSERT ( index < length );
684
while ( ( formatPos+1 ) < formatLength && index >= formatArray[formatPos+1].startPos )
687
r.character=text[index];
688
r.rendition = formatArray[formatPos].rendition;
689
r.foregroundColor = formatArray[formatPos].fgColor;
690
r.backgroundColor = formatArray[formatPos].bgColor;
693
void CompactHistoryLine::getCharacters ( Character* array, int length, int startColumn )
695
Q_ASSERT ( startColumn >= 0 && length >= 0 );
696
Q_ASSERT ( startColumn+length <= ( int ) getLength() );
698
for ( int i=startColumn; i<length+startColumn; i++ )
700
getCharacter ( i, array[i-startColumn] );
704
CompactHistoryScroll::CompactHistoryScroll ( unsigned int maxLineCount )
705
: HistoryScroll ( new CompactHistoryType ( maxLineCount ) )
709
//kDebug() << "scroll of length " << maxLineCount << " created";
710
setMaxNbLines ( maxLineCount );
713
CompactHistoryScroll::~CompactHistoryScroll()
715
qDeleteAll ( lines.begin(), lines.end() );
719
void CompactHistoryScroll::addCellsVector ( const TextLine& cells )
721
CompactHistoryLine *line;
722
line = new(blockList) CompactHistoryLine ( cells, blockList );
724
if ( lines.size() > ( int ) _maxLineCount )
726
delete lines.takeAt ( 0 );
728
lines.append ( line );
731
void CompactHistoryScroll::addCells ( const Character a[], int count )
733
TextLine newLine ( count );
734
qCopy ( a,a+count,newLine.begin() );
735
addCellsVector ( newLine );
738
void CompactHistoryScroll::addLine ( bool previousWrapped )
740
CompactHistoryLine *line = lines.last();
741
//kDebug() << "last line at address " << line;
742
line->setWrapped(previousWrapped);
745
int CompactHistoryScroll::getLines()
750
int CompactHistoryScroll::getLineLen ( int lineNumber )
752
Q_ASSERT ( lineNumber >= 0 && lineNumber < lines.size() );
753
CompactHistoryLine* line = lines[lineNumber];
754
//kDebug() << "request for line at address " << line;
755
return line->getLength();
759
void CompactHistoryScroll::getCells ( int lineNumber, int startColumn, int count, Character buffer[] )
761
if ( count == 0 ) return;
762
Q_ASSERT ( lineNumber < lines.size() );
763
CompactHistoryLine* line = lines[lineNumber];
764
Q_ASSERT ( startColumn >= 0 );
765
Q_ASSERT ( (unsigned int)startColumn <= line->getLength() - count );
766
line->getCharacters ( buffer, count, startColumn );
769
void CompactHistoryScroll::setMaxNbLines ( unsigned int lineCount )
771
_maxLineCount = lineCount;
773
while (lines.size() > (int) lineCount) {
774
delete lines.takeAt(0);
776
//kDebug() << "set max lines to: " << _maxLineCount;
779
bool CompactHistoryScroll::isWrappedLine ( int lineNumber )
781
Q_ASSERT ( lineNumber < lines.size() );
782
return lines[lineNumber]->isWrapped();
786
//////////////////////////////////////////////////////////////////////
788
//////////////////////////////////////////////////////////////////////
790
HistoryType::HistoryType()
794
HistoryType::~HistoryType()
798
//////////////////////////////
800
HistoryTypeNone::HistoryTypeNone()
804
bool HistoryTypeNone::isEnabled() const
809
HistoryScroll* HistoryTypeNone::scroll(HistoryScroll *old) const
812
return new HistoryScrollNone();
815
int HistoryTypeNone::maximumLineCount() const
820
//////////////////////////////
822
HistoryTypeBlockArray::HistoryTypeBlockArray(size_t size)
827
bool HistoryTypeBlockArray::isEnabled() const
832
int HistoryTypeBlockArray::maximumLineCount() const
837
HistoryScroll* HistoryTypeBlockArray::scroll(HistoryScroll *old) const
840
return new HistoryScrollBlockArray(m_size);
844
//////////////////////////////
846
HistoryTypeBuffer::HistoryTypeBuffer(unsigned int nbLines)
851
bool HistoryTypeBuffer::isEnabled() const
856
int HistoryTypeBuffer::maximumLineCount() const
861
HistoryScroll* HistoryTypeBuffer::scroll(HistoryScroll *old) const
865
HistoryScrollBuffer *oldBuffer = dynamic_cast<HistoryScrollBuffer*>(old);
868
oldBuffer->setMaxNbLines(m_nbLines);
872
HistoryScroll *newScroll = new HistoryScrollBuffer(m_nbLines);
873
int lines = old->getLines();
875
if (lines > (int) m_nbLines)
876
startLine = lines - m_nbLines;
878
Character line[LINE_SIZE];
879
for(int i = startLine; i < lines; i++)
881
int size = old->getLineLen(i);
882
if (size > LINE_SIZE)
884
Character *tmp_line = new Character[size];
885
old->getCells(i, 0, size, tmp_line);
886
newScroll->addCells(tmp_line, size);
887
newScroll->addLine(old->isWrappedLine(i));
892
old->getCells(i, 0, size, line);
893
newScroll->addCells(line, size);
894
newScroll->addLine(old->isWrappedLine(i));
900
return new HistoryScrollBuffer(m_nbLines);
903
//////////////////////////////
905
HistoryTypeFile::HistoryTypeFile(const QString& fileName)
906
: m_fileName(fileName)
910
bool HistoryTypeFile::isEnabled() const
915
const QString& HistoryTypeFile::getFileName() const
920
HistoryScroll* HistoryTypeFile::scroll(HistoryScroll *old) const
922
if (dynamic_cast<HistoryFile *>(old))
923
return old; // Unchanged.
925
HistoryScroll *newScroll = new HistoryScrollFile(m_fileName);
927
Character line[LINE_SIZE];
928
int lines = (old != 0) ? old->getLines() : 0;
929
for(int i = 0; i < lines; i++)
931
int size = old->getLineLen(i);
932
if (size > LINE_SIZE)
934
Character *tmp_line = new Character[size];
935
old->getCells(i, 0, size, tmp_line);
936
newScroll->addCells(tmp_line, size);
937
newScroll->addLine(old->isWrappedLine(i));
942
old->getCells(i, 0, size, line);
943
newScroll->addCells(line, size);
944
newScroll->addLine(old->isWrappedLine(i));
952
int HistoryTypeFile::maximumLineCount() const
957
//////////////////////////////
959
CompactHistoryType::CompactHistoryType ( unsigned int nbLines )
960
: m_nbLines ( nbLines )
964
bool CompactHistoryType::isEnabled() const
969
int CompactHistoryType::maximumLineCount() const
974
HistoryScroll* CompactHistoryType::scroll ( HistoryScroll *old ) const
978
CompactHistoryScroll *oldBuffer = dynamic_cast<CompactHistoryScroll*> ( old );
981
oldBuffer->setMaxNbLines ( m_nbLines );
986
return new CompactHistoryScroll ( m_nbLines );