You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
678 lines
17 KiB
678 lines
17 KiB
/* |
|
This file is part of Konsole, an X terminal. |
|
Copyright 1997,1998 by Lars Doelle <lars.doelle@on-line.de> |
|
|
|
This program is free software; you can redistribute it and/or modify |
|
it under the terms of the GNU General Public License as published by |
|
the Free Software Foundation; either version 2 of the License, or |
|
(at your option) any later version. |
|
|
|
This program is distributed in the hope that it will be useful, |
|
but WITHOUT ANY WARRANTY; without even the implied warranty of |
|
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
|
GNU General Public License for more details. |
|
|
|
You should have received a copy of the GNU General Public License |
|
along with this program; if not, write to the Free Software |
|
Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA |
|
02110-1301 USA. |
|
*/ |
|
|
|
// Own |
|
#include "History.h" |
|
|
|
// System |
|
#include <stdlib.h> |
|
#include <stdio.h> |
|
#include <sys/types.h> |
|
#include <sys/mman.h> |
|
#include <unistd.h> |
|
#include <errno.h> |
|
|
|
// KDE |
|
#include <QDebug> |
|
|
|
#include <QDir> |
|
#include <qplatformdefs.h> |
|
#include <QStandardPaths> |
|
#include <KConfigGroup> |
|
#include <KSharedConfig> |
|
|
|
// Reasonable line size |
|
static const int LINE_SIZE = 1024; |
|
|
|
using namespace Konsole; |
|
|
|
Q_GLOBAL_STATIC(QString, historyFileLocation); |
|
|
|
/* |
|
An arbitrary long scroll. |
|
|
|
One can modify the scroll only by adding either cells |
|
or newlines, but access it randomly. |
|
|
|
The model is that of an arbitrary wide typewriter scroll |
|
in that the scroll is a series of lines and each line is |
|
a series of cells with no overwriting permitted. |
|
|
|
The implementation provides arbitrary length and numbers |
|
of cells and line/column indexed read access to the scroll |
|
at constant costs. |
|
*/ |
|
|
|
// History File /////////////////////////////////////////// |
|
HistoryFile::HistoryFile() |
|
: _fd(-1), |
|
_length(0), |
|
_fileMap(0), |
|
_readWriteBalance(0) |
|
{ |
|
// Determine the temp directory once |
|
// This class is called 3 times for each "unlimited" scrollback. |
|
// This has the down-side that users must restart to |
|
// load changes (currently only 2 choices). |
|
if (!historyFileLocation.exists()) { |
|
KConfigGroup configGroup(KSharedConfig::openConfig(), "FileLocation"); |
|
if (configGroup.readEntry("scrollbackUseCacheLocation", false)) { |
|
*historyFileLocation() = QStandardPaths::writableLocation(QStandardPaths::CacheLocation); |
|
} else { |
|
*historyFileLocation() = QDir::tempPath(); |
|
} |
|
QDir().mkpath(*historyFileLocation()); |
|
} |
|
const QString tmpDir = *historyFileLocation(); |
|
const QString tmpFormat = tmpDir + QLatin1Char('/') + "konsole-XXXXXX.history"; |
|
_tmpFile.setFileTemplate(tmpFormat); |
|
if (_tmpFile.open()) { |
|
_tmpFile.setAutoRemove(true); |
|
_fd = _tmpFile.handle(); |
|
} |
|
} |
|
|
|
HistoryFile::~HistoryFile() |
|
{ |
|
if (_fileMap) |
|
unmap(); |
|
} |
|
|
|
//TODO: Mapping the entire file in will cause problems if the history file becomes exceedingly large, |
|
//(ie. larger than available memory). HistoryFile::map() should only map in sections of the file at a time, |
|
//to avoid this. |
|
void HistoryFile::map() |
|
{ |
|
Q_ASSERT(_fileMap == 0); |
|
|
|
_fileMap = (char*)mmap(0 , _length , PROT_READ , MAP_PRIVATE , _fd , 0); |
|
|
|
//if mmap'ing fails, fall back to the read-lseek combination |
|
if (_fileMap == MAP_FAILED) { |
|
_readWriteBalance = 0; |
|
_fileMap = 0; |
|
qWarning() << "mmap'ing history failed. errno = " << errno; |
|
} |
|
} |
|
|
|
void HistoryFile::unmap() |
|
{ |
|
int result = munmap(_fileMap , _length); |
|
Q_ASSERT(result == 0); |
|
Q_UNUSED(result); |
|
|
|
_fileMap = 0; |
|
} |
|
|
|
bool HistoryFile::isMapped() const |
|
{ |
|
return (_fileMap != 0); |
|
} |
|
|
|
void HistoryFile::add(const unsigned char* buffer, int count) |
|
{ |
|
if (_fileMap) |
|
unmap(); |
|
|
|
_readWriteBalance++; |
|
|
|
int rc = 0; |
|
|
|
rc = QT_LSEEK(_fd, _length, SEEK_SET); |
|
if (rc < 0) { |
|
perror("HistoryFile::add.seek"); |
|
return; |
|
} |
|
rc = write(_fd, buffer, count); |
|
if (rc < 0) { |
|
perror("HistoryFile::add.write"); |
|
return; |
|
} |
|
_length += rc; |
|
} |
|
|
|
void HistoryFile::get(unsigned char* buffer, int size, int loc) |
|
{ |
|
//count number of get() calls vs. number of add() calls. |
|
//If there are many more get() calls compared with add() |
|
//calls (decided by using MAP_THRESHOLD) then mmap the log |
|
//file to improve performance. |
|
_readWriteBalance--; |
|
if (!_fileMap && _readWriteBalance < MAP_THRESHOLD) |
|
map(); |
|
|
|
if (loc < 0 || size < 0 || loc + size > _length) { |
|
fprintf(stderr, "getHist(...,%d,%d): invalid args.\n", size, loc); |
|
return; |
|
} |
|
|
|
if (_fileMap) { |
|
for (int i = 0; i < size; i++) |
|
buffer[i] = _fileMap[loc + i]; |
|
} else { |
|
int rc = 0; |
|
|
|
rc = QT_LSEEK(_fd, loc, SEEK_SET); |
|
if (rc < 0) { |
|
perror("HistoryFile::get.seek"); |
|
return; |
|
} |
|
rc = read(_fd, buffer, size); |
|
if (rc < 0) { |
|
perror("HistoryFile::get.read"); |
|
return; |
|
} |
|
} |
|
} |
|
|
|
int HistoryFile::len() const |
|
{ |
|
return _length; |
|
} |
|
|
|
// History Scroll abstract base class ////////////////////////////////////// |
|
|
|
HistoryScroll::HistoryScroll(HistoryType* t) |
|
: _historyType(t) |
|
{ |
|
} |
|
|
|
HistoryScroll::~HistoryScroll() |
|
{ |
|
delete _historyType; |
|
} |
|
|
|
bool HistoryScroll::hasScroll() |
|
{ |
|
return true; |
|
} |
|
|
|
// History Scroll File ////////////////////////////////////// |
|
|
|
/* |
|
The history scroll makes a Row(Row(Cell)) from |
|
two history buffers. The index buffer contains |
|
start of line positions which refer to the cells |
|
buffer. |
|
|
|
Note that index[0] addresses the second line |
|
(line #1), while the first line (line #0) starts |
|
at 0 in cells. |
|
*/ |
|
|
|
HistoryScrollFile::HistoryScrollFile(const QString& logFileName) |
|
: HistoryScroll(new HistoryTypeFile(logFileName)) |
|
{ |
|
} |
|
|
|
HistoryScrollFile::~HistoryScrollFile() |
|
{ |
|
} |
|
|
|
int HistoryScrollFile::getLines() |
|
{ |
|
return _index.len() / sizeof(int); |
|
} |
|
|
|
int HistoryScrollFile::getLineLen(int lineno) |
|
{ |
|
return (startOfLine(lineno + 1) - startOfLine(lineno)) / sizeof(Character); |
|
} |
|
|
|
bool HistoryScrollFile::isWrappedLine(int lineno) |
|
{ |
|
if (lineno >= 0 && lineno <= getLines()) { |
|
unsigned char flag; |
|
_lineflags.get((unsigned char*)&flag, sizeof(unsigned char), (lineno)*sizeof(unsigned char)); |
|
return flag; |
|
} |
|
return false; |
|
} |
|
|
|
int HistoryScrollFile::startOfLine(int lineno) |
|
{ |
|
if (lineno <= 0) return 0; |
|
if (lineno <= getLines()) { |
|
if (!_index.isMapped()) |
|
_index.map(); |
|
|
|
int res; |
|
_index.get((unsigned char*)&res, sizeof(int), (lineno - 1)*sizeof(int)); |
|
return res; |
|
} |
|
return _cells.len(); |
|
} |
|
|
|
void HistoryScrollFile::getCells(int lineno, int colno, int count, Character res[]) |
|
{ |
|
_cells.get((unsigned char*)res, count * sizeof(Character), startOfLine(lineno) + colno * sizeof(Character)); |
|
} |
|
|
|
void HistoryScrollFile::addCells(const Character text[], int count) |
|
{ |
|
_cells.add((unsigned char*)text, count * sizeof(Character)); |
|
} |
|
|
|
void HistoryScrollFile::addLine(bool previousWrapped) |
|
{ |
|
if (_index.isMapped()) |
|
_index.unmap(); |
|
|
|
int locn = _cells.len(); |
|
_index.add((unsigned char*)&locn, sizeof(int)); |
|
unsigned char flags = previousWrapped ? 0x01 : 0x00; |
|
_lineflags.add((unsigned char*)&flags, sizeof(unsigned char)); |
|
} |
|
|
|
// History Scroll None ////////////////////////////////////// |
|
|
|
HistoryScrollNone::HistoryScrollNone() |
|
: HistoryScroll(new HistoryTypeNone()) |
|
{ |
|
} |
|
|
|
HistoryScrollNone::~HistoryScrollNone() |
|
{ |
|
} |
|
|
|
bool HistoryScrollNone::hasScroll() |
|
{ |
|
return false; |
|
} |
|
|
|
int HistoryScrollNone::getLines() |
|
{ |
|
return 0; |
|
} |
|
|
|
int HistoryScrollNone::getLineLen(int) |
|
{ |
|
return 0; |
|
} |
|
|
|
bool HistoryScrollNone::isWrappedLine(int /*lineno*/) |
|
{ |
|
return false; |
|
} |
|
|
|
void HistoryScrollNone::getCells(int, int, int, Character []) |
|
{ |
|
} |
|
|
|
void HistoryScrollNone::addCells(const Character [], int) |
|
{ |
|
} |
|
|
|
void HistoryScrollNone::addLine(bool) |
|
{ |
|
} |
|
|
|
//////////////////////////////////////////////////////////////// |
|
// Compact History Scroll ////////////////////////////////////// |
|
//////////////////////////////////////////////////////////////// |
|
void* CompactHistoryBlock::allocate(size_t size) |
|
{ |
|
Q_ASSERT(size > 0); |
|
if (_tail - _blockStart + size > _blockLength) |
|
return 0; |
|
|
|
void* block = _tail; |
|
_tail += size; |
|
////qDebug() << "allocated " << length << " bytes at address " << block; |
|
_allocCount++; |
|
return block; |
|
} |
|
|
|
void CompactHistoryBlock::deallocate() |
|
{ |
|
_allocCount--; |
|
Q_ASSERT(_allocCount >= 0); |
|
} |
|
|
|
void* CompactHistoryBlockList::allocate(size_t size) |
|
{ |
|
CompactHistoryBlock* block; |
|
if (list.isEmpty() || list.last()->remaining() < size) { |
|
block = new CompactHistoryBlock(); |
|
list.append(block); |
|
////qDebug() << "new block created, remaining " << block->remaining() << "number of blocks=" << list.size(); |
|
} else { |
|
block = list.last(); |
|
////qDebug() << "old block used, remaining " << block->remaining(); |
|
} |
|
return block->allocate(size); |
|
} |
|
|
|
void CompactHistoryBlockList::deallocate(void* ptr) |
|
{ |
|
Q_ASSERT(!list.isEmpty()); |
|
|
|
int i = 0; |
|
CompactHistoryBlock* block = list.at(i); |
|
while (i < list.size() && !block->contains(ptr)) { |
|
i++; |
|
block = list.at(i); |
|
} |
|
|
|
Q_ASSERT(i < list.size()); |
|
|
|
block->deallocate(); |
|
|
|
if (!block->isInUse()) { |
|
list.removeAt(i); |
|
delete block; |
|
////qDebug() << "block deleted, new size = " << list.size(); |
|
} |
|
} |
|
|
|
CompactHistoryBlockList::~CompactHistoryBlockList() |
|
{ |
|
qDeleteAll(list.begin(), list.end()); |
|
list.clear(); |
|
} |
|
|
|
void* CompactHistoryLine::operator new(size_t size, CompactHistoryBlockList& blockList) |
|
{ |
|
return blockList.allocate(size); |
|
} |
|
|
|
CompactHistoryLine::CompactHistoryLine(const TextLine& line, CompactHistoryBlockList& bList) |
|
: _blockListRef(bList), |
|
_formatArray(0), |
|
_text(0), |
|
_formatLength(0), |
|
_wrapped(false) |
|
{ |
|
_length = line.size(); |
|
|
|
if (line.size() > 0) { |
|
_formatLength = 1; |
|
int k = 1; |
|
|
|
// count number of different formats in this text line |
|
Character c = line[0]; |
|
while (k < _length) { |
|
if (!(line[k].equalsFormat(c))) { |
|
_formatLength++; // format change detected |
|
c = line[k]; |
|
} |
|
k++; |
|
} |
|
|
|
////qDebug() << "number of different formats in string: " << _formatLength; |
|
_formatArray = (CharacterFormat*) _blockListRef.allocate(sizeof(CharacterFormat) * _formatLength); |
|
Q_ASSERT(_formatArray != 0); |
|
_text = (quint16*) _blockListRef.allocate(sizeof(quint16) * line.size()); |
|
Q_ASSERT(_text != 0); |
|
|
|
_length = line.size(); |
|
_wrapped = false; |
|
|
|
// record formats and their positions in the format array |
|
c = line[0]; |
|
_formatArray[0].setFormat(c); |
|
_formatArray[0].startPos = 0; // there's always at least 1 format (for the entire line, unless a change happens) |
|
|
|
k = 1; // look for possible format changes |
|
int j = 1; |
|
while (k < _length && j < _formatLength) { |
|
if (!(line[k].equalsFormat(c))) { |
|
c = line[k]; |
|
_formatArray[j].setFormat(c); |
|
_formatArray[j].startPos = k; |
|
////qDebug() << "format entry " << j << " at pos " << _formatArray[j].startPos << " " << &(_formatArray[j].startPos) ; |
|
j++; |
|
} |
|
k++; |
|
} |
|
|
|
// copy character values |
|
for (int i = 0; i < line.size(); i++) { |
|
_text[i] = line[i].character; |
|
////qDebug() << "char " << i << " at mem " << &(text[i]); |
|
} |
|
} |
|
////qDebug() << "line created, length " << length << " at " << &(length); |
|
} |
|
|
|
CompactHistoryLine::~CompactHistoryLine() |
|
{ |
|
if (_length > 0) { |
|
_blockListRef.deallocate(_text); |
|
_blockListRef.deallocate(_formatArray); |
|
} |
|
_blockListRef.deallocate(this); |
|
} |
|
|
|
void CompactHistoryLine::getCharacter(int index, Character& r) |
|
{ |
|
Q_ASSERT(index < _length); |
|
int formatPos = 0; |
|
while ((formatPos + 1) < _formatLength && index >= _formatArray[formatPos + 1].startPos) |
|
formatPos++; |
|
|
|
r.character = _text[index]; |
|
r.rendition = _formatArray[formatPos].rendition; |
|
r.foregroundColor = _formatArray[formatPos].fgColor; |
|
r.backgroundColor = _formatArray[formatPos].bgColor; |
|
r.isRealCharacter = _formatArray[formatPos].isRealCharacter; |
|
} |
|
|
|
void CompactHistoryLine::getCharacters(Character* array, int size, int startColumn) |
|
{ |
|
Q_ASSERT(startColumn >= 0 && size >= 0); |
|
Q_ASSERT(startColumn + size <= static_cast<int>(getLength())); |
|
|
|
for (int i = startColumn; i < size + startColumn; i++) { |
|
getCharacter(i, array[i - startColumn]); |
|
} |
|
} |
|
|
|
CompactHistoryScroll::CompactHistoryScroll(unsigned int maxLineCount) |
|
: HistoryScroll(new CompactHistoryType(maxLineCount)) |
|
, _lines() |
|
, _blockList() |
|
{ |
|
////qDebug() << "scroll of length " << maxLineCount << " created"; |
|
setMaxNbLines(maxLineCount); |
|
} |
|
|
|
CompactHistoryScroll::~CompactHistoryScroll() |
|
{ |
|
qDeleteAll(_lines.begin(), _lines.end()); |
|
_lines.clear(); |
|
} |
|
|
|
void CompactHistoryScroll::addCellsVector(const TextLine& cells) |
|
{ |
|
CompactHistoryLine* line; |
|
line = new(_blockList) CompactHistoryLine(cells, _blockList); |
|
|
|
if (_lines.size() > static_cast<int>(_maxLineCount)) { |
|
delete _lines.takeAt(0); |
|
} |
|
_lines.append(line); |
|
} |
|
|
|
void CompactHistoryScroll::addCells(const Character a[], int count) |
|
{ |
|
TextLine newLine(count); |
|
qCopy(a, a + count, newLine.begin()); |
|
addCellsVector(newLine); |
|
} |
|
|
|
void CompactHistoryScroll::addLine(bool previousWrapped) |
|
{ |
|
CompactHistoryLine* line = _lines.last(); |
|
////qDebug() << "last line at address " << line; |
|
line->setWrapped(previousWrapped); |
|
} |
|
|
|
int CompactHistoryScroll::getLines() |
|
{ |
|
return _lines.size(); |
|
} |
|
|
|
int CompactHistoryScroll::getLineLen(int lineNumber) |
|
{ |
|
if ((lineNumber < 0) || (lineNumber >= _lines.size())) { |
|
//qDebug() << "requested line invalid: 0 < " << lineNumber << " < " <<_lines.size(); |
|
//Q_ASSERT(lineNumber >= 0 && lineNumber < _lines.size()); |
|
return 0; |
|
} |
|
CompactHistoryLine* line = _lines[lineNumber]; |
|
////qDebug() << "request for line at address " << line; |
|
return line->getLength(); |
|
} |
|
|
|
void CompactHistoryScroll::getCells(int lineNumber, int startColumn, int count, Character buffer[]) |
|
{ |
|
if (count == 0) return; |
|
Q_ASSERT(lineNumber < _lines.size()); |
|
CompactHistoryLine* line = _lines[lineNumber]; |
|
Q_ASSERT(startColumn >= 0); |
|
Q_ASSERT((unsigned int)startColumn <= line->getLength() - count); |
|
line->getCharacters(buffer, count, startColumn); |
|
} |
|
|
|
void CompactHistoryScroll::setMaxNbLines(unsigned int lineCount) |
|
{ |
|
_maxLineCount = lineCount; |
|
|
|
while (_lines.size() > static_cast<int>(lineCount)) { |
|
delete _lines.takeAt(0); |
|
} |
|
////qDebug() << "set max lines to: " << _maxLineCount; |
|
} |
|
|
|
bool CompactHistoryScroll::isWrappedLine(int lineNumber) |
|
{ |
|
Q_ASSERT(lineNumber < _lines.size()); |
|
return _lines[lineNumber]->isWrapped(); |
|
} |
|
|
|
////////////////////////////////////////////////////////////////////// |
|
// History Types |
|
////////////////////////////////////////////////////////////////////// |
|
|
|
HistoryType::HistoryType() |
|
{ |
|
} |
|
|
|
HistoryType::~HistoryType() |
|
{ |
|
} |
|
|
|
////////////////////////////// |
|
|
|
HistoryTypeNone::HistoryTypeNone() |
|
{ |
|
} |
|
|
|
bool HistoryTypeNone::isEnabled() const |
|
{ |
|
return false; |
|
} |
|
|
|
HistoryScroll* HistoryTypeNone::scroll(HistoryScroll* old) const |
|
{ |
|
delete old; |
|
return new HistoryScrollNone(); |
|
} |
|
|
|
int HistoryTypeNone::maximumLineCount() const |
|
{ |
|
return 0; |
|
} |
|
|
|
////////////////////////////// |
|
|
|
HistoryTypeFile::HistoryTypeFile(const QString& fileName) |
|
: _fileName(fileName) |
|
{ |
|
} |
|
|
|
bool HistoryTypeFile::isEnabled() const |
|
{ |
|
return true; |
|
} |
|
|
|
HistoryScroll* HistoryTypeFile::scroll(HistoryScroll* old) const |
|
{ |
|
if (dynamic_cast<HistoryFile *>(old)) |
|
return old; // Unchanged. |
|
|
|
HistoryScroll* newScroll = new HistoryScrollFile(_fileName); |
|
|
|
Character line[LINE_SIZE]; |
|
int lines = (old != 0) ? old->getLines() : 0; |
|
for (int i = 0; i < lines; i++) { |
|
int size = old->getLineLen(i); |
|
if (size > LINE_SIZE) { |
|
Character* tmp_line = new Character[size]; |
|
old->getCells(i, 0, size, tmp_line); |
|
newScroll->addCells(tmp_line, size); |
|
newScroll->addLine(old->isWrappedLine(i)); |
|
delete [] tmp_line; |
|
} else { |
|
old->getCells(i, 0, size, line); |
|
newScroll->addCells(line, size); |
|
newScroll->addLine(old->isWrappedLine(i)); |
|
} |
|
} |
|
|
|
delete old; |
|
return newScroll; |
|
} |
|
|
|
int HistoryTypeFile::maximumLineCount() const |
|
{ |
|
return -1; |
|
} |
|
|
|
////////////////////////////// |
|
|
|
CompactHistoryType::CompactHistoryType(unsigned int nbLines) |
|
: _maxLines(nbLines) |
|
{ |
|
} |
|
|
|
bool CompactHistoryType::isEnabled() const |
|
{ |
|
return true; |
|
} |
|
|
|
int CompactHistoryType::maximumLineCount() const |
|
{ |
|
return _maxLines; |
|
} |
|
|
|
HistoryScroll* CompactHistoryType::scroll(HistoryScroll* old) const |
|
{ |
|
if (old) { |
|
CompactHistoryScroll* oldBuffer = dynamic_cast<CompactHistoryScroll*>(old); |
|
if (oldBuffer) { |
|
oldBuffer->setMaxNbLines(_maxLines); |
|
return oldBuffer; |
|
} |
|
delete old; |
|
} |
|
return new CompactHistoryScroll(_maxLines); |
|
}
|
|
|