root/src/apps/terminal/HistoryBuffer.cpp
/*
 * 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;
        Attributes attributes;
        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.Reset();
                                nextAttributesAt = attributesRun->offset;
                        } else if (attributesRunCount > 0) {
                                attributes = attributesRun->attributes;
                                nextAttributesAt = attributesRun->offset
                                        + attributesRun->length;
                                attributesRun++;
                                attributesRunCount--;
                        } else {
                                attributes.Reset();
                                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.state |= A_WIDTH;
                        // attributes of the second, "invisible" cell must be
                        // cleared to let full-width chars detection work properly
                        buffer->cells[charCount++].attributes.Reset();
                }
        }

        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
        Attributes attributes;
        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) {
                        attributes.state = cell.attributes.state & CHAR_ATTRIBUTES;
                        attributes.foreground = cell.attributes.foreground;
                        attributes.background = cell.attributes.background;
                        if (attributes.state != 0)
                                attributesRuns++;
                }
                if (cell.attributes.IsWidth())
                        i++;
        }

//debug_printf("  attributesRuns: %ld, byteLength: %ld\n", attributesRuns, byteLength);

        // allocate and translate the line
        HistoryLine* historyLine = _AllocateLine(attributesRuns, byteLength);

        attributes.Reset();
        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) {
                        // terminate the previous attributes run
                        if (attributes.state != 0) {
                                attributesRun->length = i - attributesRun->offset;
                                attributesRun++;
                        }

                        attributes.state = cell.attributes.state & CHAR_ATTRIBUTES;
                        attributes.foreground = cell.attributes.foreground;
                        attributes.background = cell.attributes.background;

                        // init the new one
                        if (attributes.state != 0) {
                                attributesRun->attributes = attributes;
                                attributesRun->offset = i;
                        }
                }

                if (cell.attributes.IsWidth())
                        i++;
        }

        // set the last attributes run's length
        if (attributes.state != 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;
}