300 lines
6.8 KiB
C++
300 lines
6.8 KiB
C++
/*
|
|
* Copyright 2013, Haiku, Inc. All rights reserved.
|
|
* Copyright 2008, Ingo Weinhold, ingo_weinhold@gmx.de.
|
|
* Distributed under the terms of the MIT License.
|
|
*
|
|
* Authors:
|
|
* Ingo Weinhold, ingo_weinhold@gmx.de
|
|
* Siarzhuk Zharski, zharik@gmx.li
|
|
*/
|
|
|
|
#include "HistoryBuffer.h"
|
|
|
|
#include <new>
|
|
|
|
#include <OS.h>
|
|
|
|
#include "TermConst.h"
|
|
|
|
|
|
HistoryBuffer::HistoryBuffer()
|
|
:
|
|
fLines(NULL),
|
|
fWidth(0),
|
|
fCapacity(0),
|
|
fNextLine(0),
|
|
fSize(0),
|
|
fBuffer(NULL),
|
|
fBufferSize(0),
|
|
fBufferAllocationOffset(0)
|
|
{
|
|
}
|
|
|
|
|
|
HistoryBuffer::~HistoryBuffer()
|
|
{
|
|
delete[] fLines;
|
|
delete[] fBuffer;
|
|
}
|
|
|
|
|
|
status_t
|
|
HistoryBuffer::Init(int32 width, int32 capacity)
|
|
{
|
|
if (width <= 0 || capacity <= 0)
|
|
return B_BAD_VALUE;
|
|
|
|
int32 bufferSize = (width + 4) * capacity;
|
|
|
|
if (capacity > 0) {
|
|
fLines = new(std::nothrow) HistoryLine[capacity];
|
|
fBuffer = new(std::nothrow) uint8[bufferSize];
|
|
|
|
if (fLines == NULL || fBuffer == NULL)
|
|
return B_NO_MEMORY;
|
|
}
|
|
|
|
fWidth = width;
|
|
fCapacity = capacity;
|
|
fNextLine = 0;
|
|
fSize = 0;
|
|
fBufferSize = bufferSize;
|
|
fBufferAllocationOffset = 0;
|
|
|
|
return B_OK;
|
|
}
|
|
|
|
|
|
void
|
|
HistoryBuffer::Clear()
|
|
{
|
|
fNextLine = 0;
|
|
fSize = 0;
|
|
fBufferAllocationOffset = 0;
|
|
}
|
|
|
|
|
|
TerminalLine*
|
|
HistoryBuffer::GetTerminalLineAt(int32 index, TerminalLine* buffer) const
|
|
{
|
|
HistoryLine* line = LineAt(index);
|
|
if (line == NULL)
|
|
return NULL;
|
|
|
|
int32 charCount = 0;
|
|
const char* chars = line->Chars();
|
|
buffer->length = 0;
|
|
uint32 attributes = 0;
|
|
AttributesRun* attributesRun = line->AttributesRuns();
|
|
int32 attributesRunCount = line->attributesRunCount;
|
|
int32 nextAttributesAt = attributesRunCount > 0
|
|
? attributesRun->offset : INT_MAX;
|
|
|
|
for (int32 i = 0; i < line->byteLength;) {
|
|
// get attributes
|
|
if (charCount == nextAttributesAt) {
|
|
if (charCount < attributesRun->offset) {
|
|
// the "hole" in attributes run
|
|
attributes = 0;
|
|
nextAttributesAt = attributesRun->offset;
|
|
} else if (attributesRunCount > 0) {
|
|
attributes = attributesRun->attributes;
|
|
nextAttributesAt = attributesRun->offset
|
|
+ attributesRun->length;
|
|
attributesRun++;
|
|
attributesRunCount--;
|
|
} else {
|
|
attributes = 0;
|
|
nextAttributesAt = INT_MAX;
|
|
}
|
|
}
|
|
|
|
// copy character
|
|
TerminalCell& cell = buffer->cells[charCount++];
|
|
int32 charLength = UTF8Char::ByteCount(chars[i]);
|
|
cell.character.SetTo(chars + i, charLength);
|
|
i += charLength;
|
|
|
|
// set attributes
|
|
cell.attributes = attributes;
|
|
|
|
// full width char?
|
|
if (cell.character.IsFullWidth()) {
|
|
cell.attributes |= A_WIDTH;
|
|
// attributes of the second, "invisible" cell must be
|
|
// cleared to let full-width chars detection work properly
|
|
buffer->cells[charCount++].attributes = 0;
|
|
}
|
|
}
|
|
|
|
buffer->length = charCount;
|
|
buffer->softBreak = line->softBreak;
|
|
buffer->attributes = line->attributes;
|
|
|
|
return buffer;
|
|
}
|
|
|
|
|
|
void
|
|
HistoryBuffer::AddLine(const TerminalLine* line)
|
|
{
|
|
//debug_printf("HistoryBuffer::AddLine(%p): length: %d\n", line, line->length);
|
|
// determine the amount of memory we need for the line
|
|
uint32 attributes = 0;
|
|
int32 attributesRuns = 0;
|
|
int32 byteLength = 0;
|
|
for (int32 i = 0; i < line->length; i++) {
|
|
const TerminalCell& cell = line->cells[i];
|
|
byteLength += cell.character.ByteCount();
|
|
if ((cell.attributes & CHAR_ATTRIBUTES) != attributes) {
|
|
attributes = cell.attributes & CHAR_ATTRIBUTES;
|
|
if (attributes != 0)
|
|
attributesRuns++;
|
|
}
|
|
if (IS_WIDTH(cell.attributes))
|
|
i++;
|
|
}
|
|
|
|
//debug_printf(" attributesRuns: %ld, byteLength: %ld\n", attributesRuns, byteLength);
|
|
|
|
// allocate and translate the line
|
|
HistoryLine* historyLine = _AllocateLine(attributesRuns, byteLength);
|
|
|
|
attributes = 0;
|
|
AttributesRun* attributesRun = historyLine->AttributesRuns();
|
|
|
|
char* chars = historyLine->Chars();
|
|
for (int32 i = 0; i < line->length; i++) {
|
|
const TerminalCell& cell = line->cells[i];
|
|
|
|
// copy char
|
|
int32 charLength = cell.character.ByteCount();
|
|
memcpy(chars, cell.character.bytes, charLength);
|
|
chars += charLength;
|
|
|
|
// deal with attributes
|
|
if ((cell.attributes & CHAR_ATTRIBUTES) != attributes) {
|
|
// terminate the previous attributes run
|
|
if (attributes != 0) {
|
|
attributesRun->length = i - attributesRun->offset;
|
|
attributesRun++;
|
|
}
|
|
|
|
attributes = cell.attributes & CHAR_ATTRIBUTES;
|
|
|
|
// init the new one
|
|
if (attributes != 0) {
|
|
attributesRun->attributes = attributes;
|
|
attributesRun->offset = i;
|
|
}
|
|
}
|
|
|
|
if (IS_WIDTH(cell.attributes))
|
|
i++;
|
|
}
|
|
|
|
// set the last attributes run's length
|
|
if (attributes != 0)
|
|
attributesRun->length = line->length - attributesRun->offset;
|
|
|
|
historyLine->softBreak = line->softBreak;
|
|
historyLine->attributes = line->attributes;
|
|
//debug_printf(" line: \"%.*s\", history size now: %ld\n", historyLine->byteLength, historyLine->Chars(), fSize);
|
|
}
|
|
|
|
|
|
void
|
|
HistoryBuffer::AddEmptyLines(int32 count)
|
|
{
|
|
if (count <= 0)
|
|
return;
|
|
|
|
if (count > fCapacity)
|
|
count = fCapacity;
|
|
|
|
if (count + fSize > fCapacity)
|
|
DropLines(count + fSize - fCapacity);
|
|
|
|
// All lines use the same buffer address, since they don't use any memory.
|
|
AttributesRun* attributesRun
|
|
= (AttributesRun*)(fBuffer + fBufferAllocationOffset);
|
|
|
|
for (int32 i = 0; i < count; i++) {
|
|
HistoryLine* line = &fLines[fNextLine];
|
|
fNextLine = (fNextLine + 1) % fCapacity;
|
|
line->attributesRuns = attributesRun;
|
|
line->attributesRunCount = 0;
|
|
line->byteLength = 0;
|
|
line->softBreak = false;
|
|
}
|
|
|
|
fSize += count;
|
|
}
|
|
|
|
|
|
void
|
|
HistoryBuffer::DropLines(int32 count)
|
|
{
|
|
if (count <= 0)
|
|
return;
|
|
|
|
if (count < fSize) {
|
|
fSize -= count;
|
|
} else {
|
|
fSize = 0;
|
|
fNextLine = 0;
|
|
fBufferAllocationOffset = 0;
|
|
}
|
|
}
|
|
|
|
|
|
HistoryLine*
|
|
HistoryBuffer::_AllocateLine(int32 attributesRuns, int32 byteLength)
|
|
{
|
|
// we need at least one spare line slot
|
|
int32 toDrop = 0;
|
|
if (fSize == fCapacity)
|
|
toDrop = 1;
|
|
|
|
int32 bytesNeeded = attributesRuns * sizeof(AttributesRun) + byteLength;
|
|
|
|
if (fBufferAllocationOffset + bytesNeeded > fBufferSize) {
|
|
// drop all lines after the allocation index
|
|
for (; toDrop < fSize; toDrop++) {
|
|
HistoryLine* line = _LineAt(fSize - toDrop - 1);
|
|
int32 offset = (uint8*)line->AttributesRuns() - fBuffer;
|
|
if (offset < fBufferAllocationOffset)
|
|
break;
|
|
}
|
|
|
|
fBufferAllocationOffset = 0;
|
|
}
|
|
|
|
// drop all lines interfering
|
|
int32 nextOffset = (fBufferAllocationOffset + bytesNeeded + 1) & ~1;
|
|
for (; toDrop < fSize; toDrop++) {
|
|
HistoryLine* line = _LineAt(fSize - toDrop - 1);
|
|
int32 offset = (uint8*)line->AttributesRuns() - fBuffer;
|
|
if (offset + line->BufferSize() <= fBufferAllocationOffset
|
|
|| offset >= nextOffset) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
DropLines(toDrop);
|
|
|
|
// init the line
|
|
HistoryLine* line = &fLines[fNextLine];
|
|
fNextLine = (fNextLine + 1) % fCapacity;
|
|
fSize++;
|
|
line->attributesRuns = (AttributesRun*)(fBuffer + fBufferAllocationOffset);
|
|
line->attributesRunCount = attributesRuns;
|
|
line->byteLength = byteLength;
|
|
|
|
fBufferAllocationOffset = (fBufferAllocationOffset + bytesNeeded + 1) & ~1;
|
|
// DropLines() may have changed fBufferAllocationOffset, so don't use
|
|
// nextOffset.
|
|
|
|
return line;
|
|
}
|