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>
38 // Reasonable line size
39 #define LINE_SIZE 1024
41 using namespace Konsole
;
44 An arbitrary long scroll.
46 One can modify the scroll only by adding either cells
47 or newlines, but access it randomly.
49 The model is that of an arbitrary wide typewriter scroll
50 in that the scroll is a serie of lines and each line is
51 a serie of cells with no overwriting permitted.
53 The implementation provides arbitrary length and numbers
54 of cells and line/column indexed read access to the scroll
57 KDE4: Can we use QTemporaryFile here, instead of KTempFile?
59 FIXME: some complain about the history buffer comsuming the
60 memory of their machines. This problem is critical
61 since the history does not behave gracefully in cases
62 where the memory is used up completely.
64 I put in a workaround that should handle it problem
65 now gracefully. I'm not satisfied with the solution.
67 FIXME: Terminating the history is not properly indicated
68 in the menu. We should throw a signal.
70 FIXME: There is noticeable decrease in speed, also. Perhaps,
71 there whole feature needs to be revisited therefore.
72 Disadvantage of a more elaborated, say block-oriented
73 scheme with wrap around would be it's complexity.
76 //FIXME: tempory replacement for tmpfile
77 // this is here one for debugging purpose.
79 //#define tmpfile xTmpFile
81 // History File ///////////////////////////////////////////
84 A Row(X) data type which allows adding elements to the end.
87 HistoryFile::HistoryFile()
94 tmpFile
.setAutoRemove(true);
95 ion
= tmpFile
.handle();
99 HistoryFile::~HistoryFile()
105 //TODO: Mapping the entire file in will cause problems if the history file becomes exceedingly large,
106 //(ie. larger than available memory). HistoryFile::map() should only map in sections of the file at a time,
108 void HistoryFile::map()
110 assert( fileMap
== 0 );
112 fileMap
= (char*)mmap( 0 , length
, PROT_READ
, MAP_PRIVATE
, ion
, 0 );
114 //if mmap'ing fails, fall back to the read-lseek combination
115 if ( fileMap
== MAP_FAILED
)
117 readWriteBalance
= 0;
119 kDebug() << k_funcinfo
<< ": mmap'ing history failed. errno = " << errno
;
123 void HistoryFile::unmap()
125 int result
= munmap( fileMap
, length
);
126 assert( result
== 0 );
131 bool HistoryFile::isMapped()
133 return (fileMap
!= 0);
136 void HistoryFile::add(const unsigned char* bytes
, int len
)
145 rc
= KDE_lseek(ion
,length
,SEEK_SET
); if (rc
< 0) { perror("HistoryFile::add.seek"); return; }
146 rc
= write(ion
,bytes
,len
); if (rc
< 0) { perror("HistoryFile::add.write"); return; }
150 void HistoryFile::get(unsigned char* bytes
, int len
, int loc
)
152 //count number of get() calls vs. number of add() calls.
153 //If there are many more get() calls compared with add()
154 //calls (decided by using MAP_THRESHOLD) then mmap the log
155 //file to improve performance.
157 if ( !fileMap
&& readWriteBalance
< MAP_THRESHOLD
)
162 for (int i
=0;i
<len
;i
++)
163 bytes
[i
]=fileMap
[loc
+i
];
169 if (loc
< 0 || len
< 0 || loc
+ len
> length
)
170 fprintf(stderr
,"getHist(...,%d,%d): invalid args.\n",len
,loc
);
171 rc
= KDE_lseek(ion
,loc
,SEEK_SET
); if (rc
< 0) { perror("HistoryFile::get.seek"); return; }
172 rc
= read(ion
,bytes
,len
); if (rc
< 0) { perror("HistoryFile::get.read"); return; }
176 int HistoryFile::len()
182 // History Scroll abstract base class //////////////////////////////////////
185 HistoryScroll::HistoryScroll(HistoryType
* t
)
190 HistoryScroll::~HistoryScroll()
195 bool HistoryScroll::hasScroll()
200 // History Scroll File //////////////////////////////////////
203 The history scroll makes a Row(Row(Cell)) from
204 two history buffers. The index buffer contains
205 start of line positions which refere to the cells
208 Note that index[0] addresses the second line
209 (line #1), while the first line (line #0) starts
213 HistoryScrollFile::HistoryScrollFile(const QString
&logFileName
)
214 : HistoryScroll(new HistoryTypeFile(logFileName
)),
215 m_logFileName(logFileName
)
219 HistoryScrollFile::~HistoryScrollFile()
223 int HistoryScrollFile::getLines()
225 return index
.len() / sizeof(int);
228 int HistoryScrollFile::getLineLen(int lineno
)
230 return (startOfLine(lineno
+1) - startOfLine(lineno
)) / sizeof(Character
);
233 bool HistoryScrollFile::isWrappedLine(int lineno
)
235 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
);
397 dynamic_cast<HistoryTypeBuffer
*>(m_histType
)->m_nbLines
= lineCount
;
400 int HistoryScrollBuffer::bufferIndex(int lineNumber
)
402 Q_ASSERT( lineNumber
>= 0 );
403 Q_ASSERT( lineNumber
< _maxLineCount
);
404 Q_ASSERT( (_usedLines
== _maxLineCount
) || lineNumber
<= _head
);
406 if ( _usedLines
== _maxLineCount
)
408 return (_head
+lineNumber
+1) % _maxLineCount
;
417 // History Scroll None //////////////////////////////////////
419 HistoryScrollNone::HistoryScrollNone()
420 : HistoryScroll(new HistoryTypeNone())
424 HistoryScrollNone::~HistoryScrollNone()
428 bool HistoryScrollNone::hasScroll()
433 int HistoryScrollNone::getLines()
438 int HistoryScrollNone::getLineLen(int)
443 bool HistoryScrollNone::isWrappedLine(int /*lineno*/)
448 void HistoryScrollNone::getCells(int, int, int, Character
[])
452 void HistoryScrollNone::addCells(const Character
[], int)
456 void HistoryScrollNone::addLine(bool)
460 // History Scroll BlockArray //////////////////////////////////////
462 HistoryScrollBlockArray::HistoryScrollBlockArray(size_t size
)
463 : HistoryScroll(new HistoryTypeBlockArray(size
))
465 m_blockArray
.setHistorySize(size
); // nb. of lines.
468 HistoryScrollBlockArray::~HistoryScrollBlockArray()
472 int HistoryScrollBlockArray::getLines()
474 return m_lineLengths
.count();
477 int HistoryScrollBlockArray::getLineLen(int lineno
)
479 if ( m_lineLengths
.contains(lineno
) )
480 return m_lineLengths
[lineno
];
485 bool HistoryScrollBlockArray::isWrappedLine(int /*lineno*/)
490 void HistoryScrollBlockArray::getCells(int lineno
, int colno
,
491 int count
, Character res
[])
495 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
)
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