add more spacing
[personal-kdebase.git] / apps / konsole / src / History.cpp
blob7e19c76133b7e0715033eccd88d36e488ade1911
1 /*
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
18 02110-1301 USA.
21 // Own
22 #include "History.h"
24 // System
25 #include <iostream>
26 #include <stdlib.h>
27 #include <assert.h>
28 #include <stdio.h>
29 #include <sys/types.h>
30 #include <sys/mman.h>
31 #include <unistd.h>
32 #include <errno.h>
34 // KDE
35 #include <kde_file.h>
36 #include <kdebug.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
55 at constant costs.
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()
88 : ion(-1),
89 length(0),
90 fileMap(0)
92 if (tmpFile.open())
94 tmpFile.setAutoRemove(true);
95 ion = tmpFile.handle();
99 HistoryFile::~HistoryFile()
101 if (fileMap)
102 unmap();
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,
107 //to avoid this.
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;
118 fileMap = 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 );
128 fileMap = 0;
131 bool HistoryFile::isMapped()
133 return (fileMap != 0);
136 void HistoryFile::add(const unsigned char* bytes, int len)
138 if ( fileMap )
139 unmap();
141 readWriteBalance++;
143 int rc = 0;
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; }
147 length += rc;
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.
156 readWriteBalance--;
157 if ( !fileMap && readWriteBalance < MAP_THRESHOLD )
158 map();
160 if ( fileMap )
162 for (int i=0;i<len;i++)
163 bytes[i]=fileMap[loc+i];
165 else
167 int rc = 0;
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()
178 return length;
182 // History Scroll abstract base class //////////////////////////////////////
185 HistoryScroll::HistoryScroll(HistoryType* t)
186 : m_histType(t)
190 HistoryScroll::~HistoryScroll()
192 delete m_histType;
195 bool HistoryScroll::hasScroll()
197 return true;
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
206 buffer.
208 Note that index[0] addresses the second line
209 (line #1), while the first line (line #0) starts
210 at 0 in cells.
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()) {
236 unsigned char flag;
237 lineflags.get((unsigned char*)&flag,sizeof(unsigned char),(lineno)*sizeof(unsigned char));
238 return flag;
240 return false;
243 int HistoryScrollFile::startOfLine(int lineno)
245 if (lineno <= 0) return 0;
246 if (lineno <= getLines())
249 if (!index.isMapped())
250 index.map();
252 int res;
253 index.get((unsigned char*)&res,sizeof(int),(lineno-1)*sizeof(int));
254 return res;
256 return cells.len();
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())
272 index.unmap();
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))
284 ,_historyBuffer()
285 ,_maxLineCount(0)
286 ,_usedLines(0)
287 ,_head(0)
289 setMaxNbLines(maxLineCount);
292 HistoryScrollBuffer::~HistoryScrollBuffer()
294 delete[] _historyBuffer;
297 void HistoryScrollBuffer::addCellsVector(const QVector<Character>& cells)
299 _head++;
300 if ( _usedLines < _maxLineCount )
301 _usedLines++;
303 if ( _head >= _maxLineCount )
305 _head = 0;
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()
326 return _usedLines;
329 int HistoryScrollBuffer::getLineLen(int lineNumber)
331 Q_ASSERT( lineNumber >= 0 && lineNumber < _maxLineCount );
333 if ( lineNumber < _usedLines )
335 return _historyBuffer[bufferIndex(lineNumber)].size();
337 else
339 return 0;
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)];
352 else
353 return false;
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));
365 return;
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;
394 delete[] oldBuffer;
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;
410 else
412 return lineNumber;
417 // History Scroll None //////////////////////////////////////
419 HistoryScrollNone::HistoryScrollNone()
420 : HistoryScroll(new HistoryTypeNone())
424 HistoryScrollNone::~HistoryScrollNone()
428 bool HistoryScrollNone::hasScroll()
430 return false;
433 int HistoryScrollNone::getLines()
435 return 0;
438 int HistoryScrollNone::getLineLen(int)
440 return 0;
443 bool HistoryScrollNone::isWrappedLine(int /*lineno*/)
445 return false;
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];
481 else
482 return 0;
485 bool HistoryScrollBlockArray::isWrappedLine(int /*lineno*/)
487 return false;
490 void HistoryScrollBlockArray::getCells(int lineno, int colno,
491 int count, Character res[])
493 if (!count) return;
495 const Block *b = m_blockArray.at(lineno);
497 if (!b) {
498 memset(res, 0, count * sizeof(Character)); // still better than random data
499 return;
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();
510 if (!b) return;
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();
521 assert (res > 0);
522 Q_UNUSED( res );
524 m_lineLengths.insert(m_blockArray.getCurrent(), count);
527 void HistoryScrollBlockArray::addLine(bool)
531 //////////////////////////////////////////////////////////////////////
532 // History Types
533 //////////////////////////////////////////////////////////////////////
535 HistoryType::HistoryType()
539 HistoryType::~HistoryType()
543 //////////////////////////////
545 HistoryTypeNone::HistoryTypeNone()
549 bool HistoryTypeNone::isEnabled() const
551 return false;
554 HistoryScroll* HistoryTypeNone::scroll(HistoryScroll *old) const
556 delete old;
557 return new HistoryScrollNone();
560 int HistoryTypeNone::maximumLineCount() const
562 return 0;
565 //////////////////////////////
567 HistoryTypeBlockArray::HistoryTypeBlockArray(size_t size)
568 : m_size(size)
572 bool HistoryTypeBlockArray::isEnabled() const
574 return true;
577 int HistoryTypeBlockArray::maximumLineCount() const
579 return m_size;
582 HistoryScroll* HistoryTypeBlockArray::scroll(HistoryScroll *old) const
584 delete old;
585 return new HistoryScrollBlockArray(m_size);
589 //////////////////////////////
591 HistoryTypeBuffer::HistoryTypeBuffer(unsigned int nbLines)
592 : m_nbLines(nbLines)
596 bool HistoryTypeBuffer::isEnabled() const
598 return true;
601 int HistoryTypeBuffer::maximumLineCount() const
603 return m_nbLines;
606 HistoryScroll* HistoryTypeBuffer::scroll(HistoryScroll *old) const
608 if (old)
610 HistoryScrollBuffer *oldBuffer = dynamic_cast<HistoryScrollBuffer*>(old);
611 if (oldBuffer)
613 oldBuffer->setMaxNbLines(m_nbLines);
614 return oldBuffer;
617 HistoryScroll *newScroll = new HistoryScrollBuffer(m_nbLines);
618 int lines = old->getLines();
619 int startLine = 0;
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));
633 delete [] tmp_line;
635 else
637 old->getCells(i, 0, size, line);
638 newScroll->addCells(line, size);
639 newScroll->addLine(old->isWrappedLine(i));
642 delete old;
643 return newScroll;
645 return new HistoryScrollBuffer(m_nbLines);
648 //////////////////////////////
650 HistoryTypeFile::HistoryTypeFile(const QString& fileName)
651 : m_fileName(fileName)
655 bool HistoryTypeFile::isEnabled() const
657 return true;
660 const QString& HistoryTypeFile::getFileName() const
662 return m_fileName;
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));
683 delete [] tmp_line;
685 else
687 old->getCells(i, 0, size, line);
688 newScroll->addCells(line, size);
689 newScroll->addLine(old->isWrappedLine(i));
693 delete old;
694 return newScroll;
697 int HistoryTypeFile::maximumLineCount() const
699 return 0;