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
46
An arbitrary long scroll.
48
One can modify the scroll only by adding either cells
49
or newlines, but access it randomly.
51
The model is that of an arbitrary wide typewriter scroll
52
in that the scroll is a serie of lines and each line is
53
a serie of cells with no overwriting permitted.
55
The implementation provides arbitrary length and numbers
56
of cells and line/column indexed read access to the scroll
59
KDE4: Can we use QTemporaryFile here, instead of KTempFile?
61
FIXME: some complain about the history buffer comsuming the
62
memory of their machines. This problem is critical
63
since the history does not behave gracefully in cases
64
where the memory is used up completely.
66
I put in a workaround that should handle it problem
67
now gracefully. I'm not satisfied with the solution.
69
FIXME: Terminating the history is not properly indicated
70
in the menu. We should throw a signal.
72
FIXME: There is noticeable decrease in speed, also. Perhaps,
73
there whole feature needs to be revisited therefore.
74
Disadvantage of a more elaborated, say block-oriented
75
scheme with wrap around would be it's complexity.
78
//FIXME: tempory replacement for tmpfile
79
// this is here one for debugging purpose.
81
//#define tmpfile xTmpFile
83
// History File ///////////////////////////////////////////
86
A Row(X) data type which allows adding elements to the end.
89
HistoryFile::HistoryFile()
96
tmpFile.setAutoRemove(true);
97
ion = tmpFile.handle();
101
HistoryFile::~HistoryFile()
107
//TODO: Mapping the entire file in will cause problems if the history file becomes exceedingly large,
108
//(ie. larger than available memory). HistoryFile::map() should only map in sections of the file at a time,
110
void HistoryFile::map()
112
assert( fileMap == 0 );
114
fileMap = (char*)mmap( 0 , length , PROT_READ , MAP_PRIVATE , ion , 0 );
116
//if mmap'ing fails, fall back to the read-lseek combination
117
if ( fileMap == MAP_FAILED )
119
readWriteBalance = 0;
121
qDebug() << __FILE__ << __LINE__ << ": mmap'ing history failed. errno = " << errno;
125
void HistoryFile::unmap()
127
int result = munmap( fileMap , length );
128
assert( result == 0 ); Q_UNUSED( result );
133
bool HistoryFile::isMapped()
135
return (fileMap != 0);
138
void HistoryFile::add(const unsigned char* bytes, int len)
147
rc = KDE_lseek(ion,length,SEEK_SET); if (rc < 0) { perror("HistoryFile::add.seek"); return; }
148
rc = write(ion,bytes,len); if (rc < 0) { perror("HistoryFile::add.write"); return; }
152
void HistoryFile::get(unsigned char* bytes, int len, int loc)
154
//count number of get() calls vs. number of add() calls.
155
//If there are many more get() calls compared with add()
156
//calls (decided by using MAP_THRESHOLD) then mmap the log
157
//file to improve performance.
159
if ( !fileMap && readWriteBalance < MAP_THRESHOLD )
164
for (int i=0;i<len;i++)
165
bytes[i]=fileMap[loc+i];
171
if (loc < 0 || len < 0 || loc + len > length)
172
fprintf(stderr,"getHist(...,%d,%d): invalid args.\n",len,loc);
173
rc = KDE_lseek(ion,loc,SEEK_SET); if (rc < 0) { perror("HistoryFile::get.seek"); return; }
174
rc = read(ion,bytes,len); if (rc < 0) { perror("HistoryFile::get.read"); return; }
178
int HistoryFile::len()
184
// History Scroll abstract base class //////////////////////////////////////
187
HistoryScroll::HistoryScroll(HistoryType* t)
192
HistoryScroll::~HistoryScroll()
197
bool HistoryScroll::hasScroll()
202
// History Scroll File //////////////////////////////////////
205
The history scroll makes a Row(Row(Cell)) from
206
two history buffers. The index buffer contains
207
start of line positions which refere to the cells
210
Note that index[0] addresses the second line
211
(line #1), while the first line (line #0) starts
215
HistoryScrollFile::HistoryScrollFile(const QString &logFileName)
216
: HistoryScroll(new HistoryTypeFile(logFileName)),
217
m_logFileName(logFileName)
221
HistoryScrollFile::~HistoryScrollFile()
225
int HistoryScrollFile::getLines()
227
return index.len() / sizeof(int);
230
int HistoryScrollFile::getLineLen(int lineno)
232
return (startOfLine(lineno+1) - startOfLine(lineno)) / sizeof(Character);
235
bool HistoryScrollFile::isWrappedLine(int lineno)
237
if (lineno>=0 && lineno <= getLines()) {
239
lineflags.get((unsigned char*)&flag,sizeof(unsigned char),(lineno)*sizeof(unsigned char));
245
int HistoryScrollFile::startOfLine(int lineno)
247
if (lineno <= 0) return 0;
248
if (lineno <= getLines())
251
if (!index.isMapped())
255
index.get((unsigned char*)&res,sizeof(int),(lineno-1)*sizeof(int));
261
void HistoryScrollFile::getCells(int lineno, int colno, int count, Character res[])
263
cells.get((unsigned char*)res,count*sizeof(Character),startOfLine(lineno)+colno*sizeof(Character));
266
void HistoryScrollFile::addCells(const Character text[], int count)
268
cells.add((unsigned char*)text,count*sizeof(Character));
271
void HistoryScrollFile::addLine(bool previousWrapped)
273
if (index.isMapped())
276
int locn = cells.len();
277
index.add((unsigned char*)&locn,sizeof(int));
278
unsigned char flags = previousWrapped ? 0x01 : 0x00;
279
lineflags.add((unsigned char*)&flags,sizeof(unsigned char));
283
// History Scroll Buffer //////////////////////////////////////
284
HistoryScrollBuffer::HistoryScrollBuffer(unsigned int maxLineCount)
285
: HistoryScroll(new HistoryTypeBuffer(maxLineCount))
291
setMaxNbLines(maxLineCount);
294
HistoryScrollBuffer::~HistoryScrollBuffer()
296
delete[] _historyBuffer;
299
void HistoryScrollBuffer::addCellsVector(const QVector<Character>& cells)
302
if ( _usedLines < _maxLineCount )
305
if ( _head >= _maxLineCount )
310
_historyBuffer[bufferIndex(_usedLines-1)] = cells;
311
_wrappedLine[bufferIndex(_usedLines-1)] = false;
313
void HistoryScrollBuffer::addCells(const Character a[], int count)
315
HistoryLine newLine(count);
316
qCopy(a,a+count,newLine.begin());
318
addCellsVector(newLine);
321
void HistoryScrollBuffer::addLine(bool previousWrapped)
323
_wrappedLine[bufferIndex(_usedLines-1)] = previousWrapped;
326
int HistoryScrollBuffer::getLines()
331
int HistoryScrollBuffer::getLineLen(int lineNumber)
333
Q_ASSERT( lineNumber >= 0 && lineNumber < _maxLineCount );
335
if ( lineNumber < _usedLines )
337
return _historyBuffer[bufferIndex(lineNumber)].size();
345
bool HistoryScrollBuffer::isWrappedLine(int lineNumber)
347
Q_ASSERT( lineNumber >= 0 && lineNumber < _maxLineCount );
349
if (lineNumber < _usedLines)
351
//kDebug() << "Line" << lineNumber << "wrapped is" << _wrappedLine[bufferIndex(lineNumber)];
352
return _wrappedLine[bufferIndex(lineNumber)];
358
void HistoryScrollBuffer::getCells(int lineNumber, int startColumn, int count, Character buffer[])
360
if ( count == 0 ) return;
362
Q_ASSERT( lineNumber < _maxLineCount );
364
if (lineNumber >= _usedLines)
366
memset(buffer, 0, count * sizeof(Character));
370
const HistoryLine& line = _historyBuffer[bufferIndex(lineNumber)];
372
//kDebug() << "startCol " << startColumn;
373
//kDebug() << "line.size() " << line.size();
374
//kDebug() << "count " << count;
376
Q_ASSERT( startColumn <= line.size() - count );
378
memcpy(buffer, line.constData() + startColumn , count * sizeof(Character));
381
void HistoryScrollBuffer::setMaxNbLines(unsigned int lineCount)
383
HistoryLine* oldBuffer = _historyBuffer;
384
HistoryLine* newBuffer = new HistoryLine[lineCount];
386
for ( int i = 0 ; i < qMin(_usedLines,(int)lineCount) ; i++ )
388
newBuffer[i] = oldBuffer[bufferIndex(i)];
391
_usedLines = qMin(_usedLines,(int)lineCount);
392
_maxLineCount = lineCount;
393
_head = ( _usedLines == _maxLineCount ) ? 0 : _usedLines-1;
395
_historyBuffer = newBuffer;
398
_wrappedLine.resize(lineCount);
399
dynamic_cast<HistoryTypeBuffer*>(m_histType)->m_nbLines = lineCount;
402
int HistoryScrollBuffer::bufferIndex(int lineNumber)
404
Q_ASSERT( lineNumber >= 0 );
405
Q_ASSERT( lineNumber < _maxLineCount );
406
Q_ASSERT( (_usedLines == _maxLineCount) || lineNumber <= _head );
408
if ( _usedLines == _maxLineCount )
410
return (_head+lineNumber+1) % _maxLineCount;
419
// History Scroll None //////////////////////////////////////
421
HistoryScrollNone::HistoryScrollNone()
422
: HistoryScroll(new HistoryTypeNone())
426
HistoryScrollNone::~HistoryScrollNone()
430
bool HistoryScrollNone::hasScroll()
435
int HistoryScrollNone::getLines()
440
int HistoryScrollNone::getLineLen(int)
445
bool HistoryScrollNone::isWrappedLine(int /*lineno*/)
450
void HistoryScrollNone::getCells(int, int, int, Character [])
454
void HistoryScrollNone::addCells(const Character [], int)
458
void HistoryScrollNone::addLine(bool)
462
// History Scroll BlockArray //////////////////////////////////////
464
HistoryScrollBlockArray::HistoryScrollBlockArray(size_t size)
465
: HistoryScroll(new HistoryTypeBlockArray(size))
467
m_blockArray.setHistorySize(size); // nb. of lines.
470
HistoryScrollBlockArray::~HistoryScrollBlockArray()
474
int HistoryScrollBlockArray::getLines()
476
return m_lineLengths.count();
479
int HistoryScrollBlockArray::getLineLen(int lineno)
481
if ( m_lineLengths.contains(lineno) )
482
return m_lineLengths[lineno];
487
bool HistoryScrollBlockArray::isWrappedLine(int /*lineno*/)
492
void HistoryScrollBlockArray::getCells(int lineno, int colno,
493
int count, Character res[])
497
const Block *b = m_blockArray.at(lineno);
500
memset(res, 0, count * sizeof(Character)); // still better than random data
504
assert(((colno + count) * sizeof(Character)) < ENTRIES);
505
memcpy(res, b->data + (colno * sizeof(Character)), count * sizeof(Character));
508
void HistoryScrollBlockArray::addCells(const Character a[], int count)
510
Block *b = m_blockArray.lastBlock();
514
// put cells in block's data
515
assert((count * sizeof(Character)) < ENTRIES);
517
memset(b->data, 0, ENTRIES);
519
memcpy(b->data, a, count * sizeof(Character));
520
b->size = count * sizeof(Character);
522
size_t res = m_blockArray.newBlock();
526
m_lineLengths.insert(m_blockArray.getCurrent(), count);
529
void HistoryScrollBlockArray::addLine(bool)
533
////////////////////////////////////////////////////////////////
534
// Compact History Scroll //////////////////////////////////////
535
////////////////////////////////////////////////////////////////
536
void* CompactHistoryBlock::allocate ( size_t length )
538
Q_ASSERT ( length > 0 );
539
if ( tail-blockStart+length > blockLength )
544
//kDebug() << "allocated " << length << " bytes at address " << block;
549
void CompactHistoryBlock::deallocate ( )
552
Q_ASSERT ( allocCount >= 0 );
555
void* CompactHistoryBlockList::allocate(size_t size)
557
CompactHistoryBlock* block;
558
if ( list.isEmpty() || list.last()->remaining() < size)
560
block = new CompactHistoryBlock();
561
list.append ( block );
562
//kDebug() << "new block created, remaining " << block->remaining() << "number of blocks=" << list.size();
567
//kDebug() << "old block used, remaining " << block->remaining();
569
return block->allocate(size);
572
void CompactHistoryBlockList::deallocate(void* ptr)
574
Q_ASSERT( !list.isEmpty());
577
CompactHistoryBlock *block = list.at(i);
578
while ( i<list.size() && !block->contains(ptr) )
584
Q_ASSERT( i<list.size() );
588
if (!block->isInUse())
592
//kDebug() << "block deleted, new size = " << list.size();
596
CompactHistoryBlockList::~CompactHistoryBlockList()
598
qDeleteAll ( list.begin(), list.end() );
602
void* CompactHistoryLine::operator new (size_t size, CompactHistoryBlockList& blockList)
604
return blockList.allocate(size);
607
CompactHistoryLine::CompactHistoryLine ( const TextLine& line, CompactHistoryBlockList& bList )
613
if (line.size() > 0) {
617
// count number of different formats in this text line
618
Character c = line[0];
621
if ( !(line[k].equalsFormat(c)))
623
formatLength++; // format change detected
629
//kDebug() << "number of different formats in string: " << formatLength;
630
formatArray = (CharacterFormat*) blockList.allocate(sizeof(CharacterFormat)*formatLength);
631
Q_ASSERT (formatArray!=NULL);
632
text = (quint16*) blockList.allocate(sizeof(quint16)*line.size());
633
Q_ASSERT (text!=NULL);
636
formatLength=formatLength;
639
// record formats and their positions in the format array
641
formatArray[0].setFormat ( c );
642
formatArray[0].startPos=0; // there's always at least 1 format (for the entire line, unless a change happens)
644
k=1; // look for possible format changes
646
while ( k<length && j<formatLength )
648
if (!(line[k].equalsFormat(c)))
651
formatArray[j].setFormat(c);
652
formatArray[j].startPos=k;
653
//kDebug() << "format entry " << j << " at pos " << formatArray[j].startPos << " " << &(formatArray[j].startPos) ;
659
// copy character values
660
for ( int i=0; i<line.size(); i++ )
662
text[i]=line[i].character;
663
//kDebug() << "char " << i << " at mem " << &(text[i]);
666
//kDebug() << "line created, length " << length << " at " << &(length);
669
CompactHistoryLine::~CompactHistoryLine()
671
//kDebug() << "~CHL";
673
blockList.deallocate(text);
674
blockList.deallocate(formatArray);
676
blockList.deallocate(this);
679
void CompactHistoryLine::getCharacter ( int index, Character &r )
681
Q_ASSERT ( index < length );
683
while ( ( formatPos+1 ) < formatLength && index >= formatArray[formatPos+1].startPos )
686
r.character=text[index];
687
r.rendition = formatArray[formatPos].rendition;
688
r.foregroundColor = formatArray[formatPos].fgColor;
689
r.backgroundColor = formatArray[formatPos].bgColor;
692
void CompactHistoryLine::getCharacters ( Character* array, int length, int startColumn )
694
Q_ASSERT ( startColumn >= 0 && length >= 0 );
695
Q_ASSERT ( startColumn+length <= ( int ) getLength() );
697
for ( int i=startColumn; i<length+startColumn; i++ )
699
getCharacter ( i, array[i-startColumn] );
703
CompactHistoryScroll::CompactHistoryScroll ( unsigned int maxLineCount )
704
: HistoryScroll ( new CompactHistoryType ( maxLineCount ) )
708
//kDebug() << "scroll of length " << maxLineCount << " created";
709
setMaxNbLines ( maxLineCount );
712
CompactHistoryScroll::~CompactHistoryScroll()
714
qDeleteAll ( lines.begin(), lines.end() );
718
void CompactHistoryScroll::addCellsVector ( const TextLine& cells )
720
CompactHistoryLine *line;
721
line = new(blockList) CompactHistoryLine ( cells, blockList );
723
if ( lines.size() > ( int ) _maxLineCount )
725
delete lines.takeAt ( 0 );
727
lines.append ( line );
730
void CompactHistoryScroll::addCells ( const Character a[], int count )
732
TextLine newLine ( count );
733
qCopy ( a,a+count,newLine.begin() );
734
addCellsVector ( newLine );
737
void CompactHistoryScroll::addLine ( bool previousWrapped )
739
CompactHistoryLine *line = lines.last();
740
//kDebug() << "last line at address " << line;
741
line->setWrapped(previousWrapped);
744
int CompactHistoryScroll::getLines()
749
int CompactHistoryScroll::getLineLen ( int lineNumber )
751
Q_ASSERT ( lineNumber >= 0 && lineNumber < lines.size() );
752
CompactHistoryLine* line = lines[lineNumber];
753
//kDebug() << "request for line at address " << line;
754
return line->getLength();
758
void CompactHistoryScroll::getCells ( int lineNumber, int startColumn, int count, Character buffer[] )
760
if ( count == 0 ) return;
761
Q_ASSERT ( lineNumber < lines.size() );
762
CompactHistoryLine* line = lines[lineNumber];
763
Q_ASSERT ( startColumn >= 0 );
764
Q_ASSERT ( (unsigned int)startColumn <= line->getLength() - count );
765
line->getCharacters ( buffer, count, startColumn );
768
void CompactHistoryScroll::setMaxNbLines ( unsigned int lineCount )
770
_maxLineCount = lineCount;
772
while (lines.size() > (int) lineCount) {
773
delete lines.takeAt(0);
775
//kDebug() << "set max lines to: " << _maxLineCount;
778
bool CompactHistoryScroll::isWrappedLine ( int lineNumber )
780
Q_ASSERT ( lineNumber < lines.size() );
781
return lines[lineNumber]->isWrapped();
785
//////////////////////////////////////////////////////////////////////
787
//////////////////////////////////////////////////////////////////////
789
HistoryType::HistoryType()
793
HistoryType::~HistoryType()
797
//////////////////////////////
799
HistoryTypeNone::HistoryTypeNone()
803
bool HistoryTypeNone::isEnabled() const
808
HistoryScroll* HistoryTypeNone::scroll(HistoryScroll *old) const
811
return new HistoryScrollNone();
814
int HistoryTypeNone::maximumLineCount() const
819
//////////////////////////////
821
HistoryTypeBlockArray::HistoryTypeBlockArray(size_t size)
826
bool HistoryTypeBlockArray::isEnabled() const
831
int HistoryTypeBlockArray::maximumLineCount() const
836
HistoryScroll* HistoryTypeBlockArray::scroll(HistoryScroll *old) const
839
return new HistoryScrollBlockArray(m_size);
843
//////////////////////////////
845
HistoryTypeBuffer::HistoryTypeBuffer(unsigned int nbLines)
850
bool HistoryTypeBuffer::isEnabled() const
855
int HistoryTypeBuffer::maximumLineCount() const
860
HistoryScroll* HistoryTypeBuffer::scroll(HistoryScroll *old) const
864
HistoryScrollBuffer *oldBuffer = dynamic_cast<HistoryScrollBuffer*>(old);
867
oldBuffer->setMaxNbLines(m_nbLines);
871
HistoryScroll *newScroll = new HistoryScrollBuffer(m_nbLines);
872
int lines = old->getLines();
874
if (lines > (int) m_nbLines)
875
startLine = lines - m_nbLines;
877
Character line[LINE_SIZE];
878
for(int i = startLine; i < lines; i++)
880
int size = old->getLineLen(i);
881
if (size > LINE_SIZE)
883
Character *tmp_line = new Character[size];
884
old->getCells(i, 0, size, tmp_line);
885
newScroll->addCells(tmp_line, size);
886
newScroll->addLine(old->isWrappedLine(i));
891
old->getCells(i, 0, size, line);
892
newScroll->addCells(line, size);
893
newScroll->addLine(old->isWrappedLine(i));
899
return new HistoryScrollBuffer(m_nbLines);
902
//////////////////////////////
904
HistoryTypeFile::HistoryTypeFile(const QString& fileName)
905
: m_fileName(fileName)
909
bool HistoryTypeFile::isEnabled() const
914
const QString& HistoryTypeFile::getFileName() const
919
HistoryScroll* HistoryTypeFile::scroll(HistoryScroll *old) const
921
if (dynamic_cast<HistoryFile *>(old))
922
return old; // Unchanged.
924
HistoryScroll *newScroll = new HistoryScrollFile(m_fileName);
926
Character line[LINE_SIZE];
927
int lines = (old != 0) ? old->getLines() : 0;
928
for(int i = 0; i < lines; i++)
930
int size = old->getLineLen(i);
931
if (size > LINE_SIZE)
933
Character *tmp_line = new Character[size];
934
old->getCells(i, 0, size, tmp_line);
935
newScroll->addCells(tmp_line, size);
936
newScroll->addLine(old->isWrappedLine(i));
941
old->getCells(i, 0, size, line);
942
newScroll->addCells(line, size);
943
newScroll->addLine(old->isWrappedLine(i));
951
int HistoryTypeFile::maximumLineCount() const
956
//////////////////////////////
958
CompactHistoryType::CompactHistoryType ( unsigned int nbLines )
959
: m_nbLines ( nbLines )
963
bool CompactHistoryType::isEnabled() const
968
int CompactHistoryType::maximumLineCount() const
973
HistoryScroll* CompactHistoryType::scroll ( HistoryScroll *old ) const
977
CompactHistoryScroll *oldBuffer = dynamic_cast<CompactHistoryScroll*> ( old );
980
oldBuffer->setMaxNbLines ( m_nbLines );
985
return new CompactHistoryScroll ( m_nbLines );