root/src/apps/terminal/BasicTerminalBuffer.cpp
/*
 * Copyright 2013-2024, Haiku, Inc. All rights reserved.
 * Copyright 2008-2010, 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 "BasicTerminalBuffer.h"

#include <alloca.h>
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>

#include <algorithm>

#include <StackOrHeapArray.h>
#include <String.h>

#include "TermConst.h"
#include "TerminalCharClassifier.h"
#include "TerminalLine.h"


static const UTF8Char kSpaceChar(' ');

// Soft size limits for the terminal buffer. The constants defined in
// TermConst.h are rather for the Terminal in general (i.e. the GUI).
static const int32 kMinRowCount = 2;
static const int32 kMaxRowCount = 1024;
static const int32 kMinColumnCount = 4;
static const int32 kMaxColumnCount = 1024;


#define ALLOC_LINE_ON_STACK(width)      \
        ((TerminalLine*)alloca(sizeof(TerminalLine)     \
                + sizeof(TerminalCell) * ((width) - 1)))


static inline int32
restrict_value(int32 value, int32 min, int32 max)
{
        return value < min ? min : (value > max ? max : value);
}


// #pragma mark - private inline methods


inline int32
BasicTerminalBuffer::_LineIndex(int32 index) const
{
        return (index + fScreenOffset) % fHeight;
}


inline TerminalLine*
BasicTerminalBuffer::_LineAt(int32 index) const
{
        return fScreen[_LineIndex(index)];
}


inline TerminalLine*
BasicTerminalBuffer::_HistoryLineAt(int32 index, TerminalLine* lineBuffer) const
{
        if (index >= fHeight)
                return NULL;

        if (index < 0 && fHistory != NULL)
                return fHistory->GetTerminalLineAt(-index - 1, lineBuffer);

        return _LineAt(index + fHeight);
}


inline void
BasicTerminalBuffer::_Invalidate(int32 top, int32 bottom)
{
//debug_printf("%p->BasicTerminalBuffer::_Invalidate(%ld, %ld)\n", this, top, bottom);
        fDirtyInfo.ExtendDirtyRegion(top, bottom);

        if (!fDirtyInfo.messageSent) {
                NotifyListener();
                fDirtyInfo.messageSent = true;
        }
}


inline void
BasicTerminalBuffer::_CursorChanged()
{
        if (!fDirtyInfo.messageSent) {
                NotifyListener();
                fDirtyInfo.messageSent = true;
        }
}


// #pragma mark - public methods


BasicTerminalBuffer::BasicTerminalBuffer()
        :
        fWidth(0),
        fHeight(0),
        fScrollTop(0),
        fScrollBottom(0),
        fScreen(NULL),
        fScreenOffset(0),
        fHistory(NULL),
        fAttributes(),
        fSoftWrappedCursor(false),
        fOverwriteMode(false),
        fAlternateScreenActive(false),
        fOriginMode(false),
        fSavedOriginMode(false),
        fTabStops(NULL),
        fEncoding(M_UTF8),
        fCaptureFile(-1),
        fLast()
{
}


BasicTerminalBuffer::~BasicTerminalBuffer()
{
        delete fHistory;
        _FreeLines(fScreen, fHeight);
        delete[] fTabStops;

        if (fCaptureFile >= 0)
                close(fCaptureFile);
}


status_t
BasicTerminalBuffer::Init(int32 width, int32 height, int32 historySize)
{
        status_t error;

        fWidth = width;
        fHeight = height;

        fScrollTop = 0;
        fScrollBottom = fHeight - 1;

        fCursor.x = 0;
        fCursor.y = 0;
        fSoftWrappedCursor = false;

        fScreenOffset = 0;

        fOverwriteMode = true;
        fAlternateScreenActive = false;
        fOriginMode = fSavedOriginMode = false;

        fScreen = _AllocateLines(width, height);
        if (fScreen == NULL)
                return B_NO_MEMORY;

        if (historySize > 0) {
                fHistory = new(std::nothrow) HistoryBuffer;
                if (fHistory == NULL)
                        return B_NO_MEMORY;

                error = fHistory->Init(width, historySize);
                if (error != B_OK)
                        return error;
        }

        error = _ResetTabStops(fWidth);
        if (error != B_OK)
                return error;

        for (int32 i = 0; i < fHeight; i++)
                fScreen[i]->Clear();

        fDirtyInfo.Reset();

        return B_OK;
}


status_t
BasicTerminalBuffer::ResizeTo(int32 width, int32 height)
{
        return ResizeTo(width, height, fHistory != NULL ? fHistory->Capacity() : 0);
}


status_t
BasicTerminalBuffer::ResizeTo(int32 width, int32 height, int32 historyCapacity)
{
        if (height < kMinRowCount || height > kMaxRowCount
                        || width < kMinColumnCount || width > kMaxColumnCount) {
                return B_BAD_VALUE;
        }

        if (width == fWidth && height == fHeight)
                return SetHistoryCapacity(historyCapacity);

        if (fAlternateScreenActive)
                return _ResizeSimple(width, height, historyCapacity);

        return _ResizeRewrap(width, height, historyCapacity);
}


status_t
BasicTerminalBuffer::SetHistoryCapacity(int32 historyCapacity)
{
        return _ResizeHistory(fWidth, historyCapacity);
}


void
BasicTerminalBuffer::Clear(bool resetCursor)
{
        fSoftWrappedCursor = false;
        fScreenOffset = 0;
        _ClearLines(0, fHeight - 1);

        if (resetCursor)
                fCursor.SetTo(0, 0);

        if (fHistory != NULL)
                fHistory->Clear();

        fDirtyInfo.linesScrolled = 0;
        _Invalidate(0, fHeight - 1);
}


void
BasicTerminalBuffer::SynchronizeWith(const BasicTerminalBuffer* other,
        int32 offset, int32 dirtyTop, int32 dirtyBottom)
{
//debug_printf("BasicTerminalBuffer::SynchronizeWith(%p, %ld, %ld - %ld)\n",
//other, offset, dirtyTop, dirtyBottom);

        // intersect the visible region with the dirty region
        int32 first = 0;
        int32 last = fHeight - 1;
        dirtyTop -= offset;
        dirtyBottom -= offset;

        if (first > dirtyBottom || dirtyTop > last)
                return;

        if (first < dirtyTop)
                first = dirtyTop;
        if (last > dirtyBottom)
                last = dirtyBottom;

        // update the dirty lines
//debug_printf("  updating: %ld - %ld\n", first, last);
        for (int32 i = first; i <= last; i++) {
                TerminalLine* destLine = _LineAt(i);
                TerminalLine* sourceLine = other->_HistoryLineAt(i + offset, destLine);
                if (sourceLine != NULL) {
                        if (sourceLine != destLine) {
                                destLine->length = sourceLine->length;
                                destLine->attributes = sourceLine->attributes;
                                destLine->softBreak = sourceLine->softBreak;
                                if (destLine->length > 0) {
                                        memcpy(destLine->cells, sourceLine->cells,
                                                fWidth * sizeof(TerminalCell));
                                }
                        } else {
                                // The source line was a history line and has been copied
                                // directly into destLine.
                        }
                } else
                        destLine->Clear(fAttributes, fWidth);
        }
}


bool
BasicTerminalBuffer::IsFullWidthChar(int32 row, int32 column) const
{
        TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
        TerminalLine* line = _HistoryLineAt(row, lineBuffer);
        return line != NULL && column > 0 && column < line->length
                && line->cells[column - 1].attributes.IsWidth();
}


int
BasicTerminalBuffer::GetChar(int32 row, int32 column, UTF8Char& character,
        Attributes& attributes) const
{
        TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
        TerminalLine* line = _HistoryLineAt(row, lineBuffer);
        if (line == NULL)
                return NO_CHAR;

        if (column < 0 || column >= line->length)
                return NO_CHAR;

        if (column > 0 && line->cells[column - 1].attributes.IsWidth())
                return IN_STRING;

        TerminalCell& cell = line->cells[column];
        character = cell.character;
        attributes = cell.attributes;
        return A_CHAR;
}


void
BasicTerminalBuffer::GetCellAttributes(int32 row, int32 column,
        Attributes& attributes, uint32& count) const
{
        count = 0;
        TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
        TerminalLine* line = _HistoryLineAt(row, lineBuffer);
        if (line == NULL || column < 0)
                return;

        int32 c = column;
        for (; c < fWidth; c++) {
                TerminalCell& cell = line->cells[c];
                if (c > column && attributes != cell.attributes)
                        break;
                attributes = cell.attributes;
        }
        count = c - column;
}


int32
BasicTerminalBuffer::GetString(int32 row, int32 firstColumn, int32 lastColumn,
        char* buffer, Attributes& attributes) const
{
        TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
        TerminalLine* line = _HistoryLineAt(row, lineBuffer);
        if (line == NULL)
                return 0;

        if (lastColumn >= line->length)
                lastColumn = line->length - 1;

        int32 column = firstColumn;
        if (column <= lastColumn)
                attributes = line->cells[column].attributes;

        for (; column <= lastColumn; column++) {
                TerminalCell& cell = line->cells[column];
                if (cell.attributes != attributes)
                        break;

                int32 bytes = cell.character.ByteCount();
                for (int32 i = 0; i < bytes; i++)
                        *buffer++ = cell.character.bytes[i];
        }

        *buffer = '\0';

        return column - firstColumn;
}


void
BasicTerminalBuffer::GetStringFromRegion(BString& string, const TermPos& start,
        const TermPos& end) const
{
//debug_printf("BasicTerminalBuffer::GetStringFromRegion((%ld, %ld), (%ld, %ld))\n",
//start.x, start.y, end.x, end.y);
        if (start >= end)
                return;

        TermPos pos(start);

        if (IsFullWidthChar(pos.y, pos.x))
                pos.x--;

        // get all but the last line
        while (pos.y < end.y) {
                TerminalLine* line = _GetPartialLineString(string, pos.y, pos.x,
                        fWidth);
                if (line != NULL && !line->softBreak)
                        string.Append('\n', 1);
                pos.x = 0;
                pos.y++;
        }

        // get the last line, if not empty
        if (end.x > 0)
                _GetPartialLineString(string, end.y, pos.x, end.x);
}


bool
BasicTerminalBuffer::FindWord(const TermPos& pos,
        TerminalCharClassifier* classifier, bool findNonWords, TermPos& _start,
        TermPos& _end) const
{
        int32 x = pos.x;
        int32 y = pos.y;

        TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
        TerminalLine* line = _HistoryLineAt(y, lineBuffer);
        if (line == NULL || x < 0 || x >= fWidth)
                return false;

        if (x >= line->length) {
                // beyond the end of the line -- select all space
                if (!findNonWords)
                        return false;

                _start.SetTo(line->length, y);
                _end.SetTo(fWidth, y);
                return true;
        }

        if (x > 0 && line->cells[x - 1].attributes.IsWidth())
                x--;

        // get the char type at the given position
        int type = classifier->Classify(line->cells[x].character);

        // check whether we are supposed to find words only
        if (type != CHAR_TYPE_WORD_CHAR && !findNonWords)
                return false;

        // find the beginning
        TermPos start(x, y);
        TermPos end(x + (line->cells[x].attributes.IsWidth()
                                ? FULL_WIDTH : HALF_WIDTH), y);
        for (;;) {
                TermPos previousPos = start;
                if (!_PreviousLinePos(lineBuffer, line, previousPos)
                        || classifier->Classify(line->cells[previousPos.x].character)
                                != type) {
                        break;
                }

                start = previousPos;
        }

        // find the end
        line = _HistoryLineAt(end.y, lineBuffer);

        for (;;) {
                TermPos nextPos = end;
                if (!_NormalizeLinePos(lineBuffer, line, nextPos))
                        break;

                if (classifier->Classify(line->cells[nextPos.x].character) != type)
                        break;

                nextPos.x += line->cells[nextPos.x].attributes.IsWidth()
                        ? FULL_WIDTH : HALF_WIDTH;
                end = nextPos;
        }

        _start = start;
        _end = end;
        return true;
}


bool
BasicTerminalBuffer::PreviousLinePos(TermPos& pos) const
{
        TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
        TerminalLine* line = _HistoryLineAt(pos.y, lineBuffer);
        if (line == NULL || pos.x < 0 || pos.x >= fWidth)
                return false;

        return _PreviousLinePos(lineBuffer, line, pos);
}


bool
BasicTerminalBuffer::NextLinePos(TermPos& pos, bool normalize) const
{
        TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
        TerminalLine* line = _HistoryLineAt(pos.y, lineBuffer);
        if (line == NULL || pos.x < 0 || pos.x > fWidth)
                return false;

        if (!_NormalizeLinePos(lineBuffer, line, pos))
                return false;

        pos.x += line->cells[pos.x].attributes.IsWidth() ? FULL_WIDTH : HALF_WIDTH;
        return !normalize || _NormalizeLinePos(lineBuffer, line, pos);
}


int32
BasicTerminalBuffer::LineLength(int32 index) const
{
        TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
        TerminalLine* line = _HistoryLineAt(index, lineBuffer);
        return line != NULL ? line->length : 0;
}


void
BasicTerminalBuffer::GetLineColor(int32 index, Attributes& attr) const
{
        TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
        TerminalLine* line = _HistoryLineAt(index, lineBuffer);
        if (line != NULL)
                attr = line->attributes;
        else
                attr.Reset();
}


bool
BasicTerminalBuffer::Find(const char* _pattern, const TermPos& start,
        bool forward, bool caseSensitive, bool matchWord, TermPos& _matchStart,
        TermPos& _matchEnd) const
{
//debug_printf("BasicTerminalBuffer::Find(\"%s\", (%ld, %ld), forward: %d, case: %d, "
//"word: %d)\n", _pattern, start.x, start.y, forward, caseSensitive, matchWord);
        // normalize pos, so that _NextChar() and _PreviousChar() are happy
        TermPos pos(start);
        TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
        TerminalLine* line = _HistoryLineAt(pos.y, lineBuffer);
        if (line != NULL) {
                if (forward) {
                        while (line != NULL && pos.x >= line->length && line->softBreak) {
                                pos.x = 0;
                                pos.y++;
                                line = _HistoryLineAt(pos.y, lineBuffer);
                        }
                } else {
                        if (pos.x > line->length)
                                pos.x = line->length;
                }
        }

        int32 patternByteLen = strlen(_pattern);

        // convert pattern to UTF8Char array
        BStackOrHeapArray<UTF8Char, 64> pattern(patternByteLen);
        if (!pattern.IsValid())
                return false;
        int32 patternLen = 0;
        while (*_pattern != '\0') {
                int32 charLen = UTF8Char::ByteCount(*_pattern);
                if (charLen > 0) {
                        pattern[patternLen].SetTo(_pattern, charLen);

                        // if not case sensitive, convert to lower case
                        if (!caseSensitive && charLen == 1)
                                pattern[patternLen] = pattern[patternLen].ToLower();

                        patternLen++;
                        _pattern += charLen;
                } else
                        _pattern++;
        }
//debug_printf("  pattern byte len: %ld, pattern len: %ld\n", patternByteLen, patternLen);

        if (patternLen == 0)
                return false;

        // reverse pattern, if searching backward
        if (!forward) {
                for (int32 i = 0; i < patternLen / 2; i++)
                        std::swap(pattern[i], pattern[patternLen - i - 1]);
        }

        // search loop
        int32 matchIndex = 0;
        TermPos matchStart;
        while (true) {
//debug_printf("    (%ld, %ld): matchIndex: %ld\n", pos.x, pos.y, matchIndex);
                TermPos previousPos(pos);
                UTF8Char c;
                if (!(forward ? _NextChar(pos, c) : _PreviousChar(pos, c)))
                        return false;

                if (caseSensitive ? (c == pattern[matchIndex])
                                : (c.ToLower() == pattern[matchIndex])) {
                        if (matchIndex == 0)
                                matchStart = previousPos;

                        matchIndex++;

                        if (matchIndex == patternLen) {
//debug_printf("      match!\n");
                                // compute the match range
                                TermPos matchEnd(pos);
                                if (!forward)
                                        std::swap(matchStart, matchEnd);

                                // check word match
                                if (matchWord) {
                                        TermPos tempPos(matchStart);
                                        if ((_PreviousChar(tempPos, c) && !c.IsSpace())
                                                || (_NextChar(tempPos = matchEnd, c) && !c.IsSpace())) {
//debug_printf("      but no word match!\n");
                                                continue;
                                        }
                                }

                                _matchStart = matchStart;
                                _matchEnd = matchEnd;
//debug_printf("  -> (%ld, %ld) - (%ld, %ld)\n", matchStart.x, matchStart.y,
//matchEnd.x, matchEnd.y);
                                return true;
                        }
                } else if (matchIndex > 0) {
                        // continue after the position where we started matching
                        pos = matchStart;
                        if (forward)
                                _NextChar(pos, c);
                        else
                                _PreviousChar(pos, c);
                        matchIndex = 0;
                }
        }
}


void
BasicTerminalBuffer::InsertChar(UTF8Char c)
{
//debug_printf("BasicTerminalBuffer::InsertChar('%.*s' (%d), %#lx)\n",
//(int)c.ByteCount(), c.bytes, c.bytes[0], attributes);
        fLast = c;
        int32 width = c.IsFullWidth() ? FULL_WIDTH : HALF_WIDTH;

        if (fSoftWrappedCursor || (fCursor.x + width) > fWidth)
                _SoftBreakLine();
        else
                _PadLineToCursor();

        fSoftWrappedCursor = false;

        if (!fOverwriteMode)
                _InsertGap(width);

        TerminalLine* line = _LineAt(fCursor.y);
        line->cells[fCursor.x].character = c;
        line->cells[fCursor.x].attributes = fAttributes;
        line->cells[fCursor.x].attributes.state |= (width == FULL_WIDTH ? A_WIDTH : 0);

        if (line->length < fCursor.x + width)
                line->length = fCursor.x + width;

        _Invalidate(fCursor.y, fCursor.y);

        fCursor.x += width;

// TODO: Deal correctly with full-width chars! We must take care not to
// overwrite half of a full-width char. This holds also for other methods.

        if (fCursor.x == fWidth) {
                fCursor.x -= width;
                fSoftWrappedCursor = true;
        }
}


void
BasicTerminalBuffer::FillScreen(UTF8Char c, Attributes &attributes)
{
        uint32 width = HALF_WIDTH;
        if (c.IsFullWidth()) {
                attributes |= A_WIDTH;
                width = FULL_WIDTH;
        }

        fSoftWrappedCursor = false;

        for (int32 y = 0; y < fHeight; y++) {
                TerminalLine *line = _LineAt(y);
                for (int32 x = 0; x < fWidth / (int32)width; x++) {
                        line->cells[x].character = c;
                        line->cells[x].attributes = attributes;
                }
                line->length = fWidth / width;
        }

        _Invalidate(0, fHeight - 1);
}


void
BasicTerminalBuffer::InsertCR()
{
        TerminalLine* line = _LineAt(fCursor.y);

        line->attributes = fAttributes;
        line->softBreak = false;
        fSoftWrappedCursor = false;
        fCursor.x = 0;
        _Invalidate(fCursor.y, fCursor.y);
        _CursorChanged();
}


void
BasicTerminalBuffer::InsertLF()
{
        fSoftWrappedCursor = false;

        // If we're at the end of the scroll region, scroll. Otherwise just advance
        // the cursor.
        if (fCursor.y == fScrollBottom) {
                _Scroll(fScrollTop, fScrollBottom, 1);
        } else {
                if (fCursor.y < fHeight - 1)
                        fCursor.y++;
                _CursorChanged();
        }
}


void
BasicTerminalBuffer::InsertRI()
{
        fSoftWrappedCursor = false;

        // If we're at the beginning of the scroll region, scroll. Otherwise just
        // reverse the cursor.
        if (fCursor.y == fScrollTop) {
                _Scroll(fScrollTop, fScrollBottom, -1);
        } else {
                if (fCursor.y > 0)
                        fCursor.y--;
                _CursorChanged();
        }
}


void
BasicTerminalBuffer::InsertTab()
{
        int32 x;

        fSoftWrappedCursor = false;

        // Find the next tab stop
        for (x = fCursor.x + 1; x < fWidth && !fTabStops[x]; x++)
                ;
        // Ensure x stayx within the line bounds
        x = restrict_value(x, 0, fWidth - 1);

        if (x != fCursor.x) {
                TerminalLine* line = _LineAt(fCursor.y);
                for (int32 i = fCursor.x; i <= x; i++) {
                        if (line->length <= i) {
                                line->cells[i].character = ' ';
                                line->cells[i].attributes = fAttributes;
                        }
                }
                fCursor.x = x;
                if (line->length < fCursor.x)
                        line->length = fCursor.x;
                _CursorChanged();
        }
}


void
BasicTerminalBuffer::InsertCursorBackTab(int32 numTabs)
{
        int32 x = fCursor.x - 1;

        fSoftWrappedCursor = false;

        // Find the next tab stop
        while (numTabs-- > 0)
                for (; x >=0 && !fTabStops[x]; x--)
                        ;
        // Ensure x stays within the line bounds
        x = restrict_value(x, 0, fWidth - 1);

        if (x != fCursor.x) {
                fCursor.x = x;
                _CursorChanged();
        }
}


void
BasicTerminalBuffer::InsertLines(int32 numLines)
{
        if (fCursor.y >= fScrollTop && fCursor.y < fScrollBottom) {
                fSoftWrappedCursor = false;
                _Scroll(fCursor.y, fScrollBottom, -numLines);
        }
}


void
BasicTerminalBuffer::SetInsertMode(int flag)
{
        fOverwriteMode = flag == MODE_OVER;
}


void
BasicTerminalBuffer::InsertSpace(int32 num)
{
// TODO: Deal with full-width chars!
        if (fCursor.x + num > fWidth)
                num = fWidth - fCursor.x;

        if (num > 0) {
                fSoftWrappedCursor = false;
                _PadLineToCursor();
                _InsertGap(num);

                TerminalLine* line = _LineAt(fCursor.y);
                for (int32 i = fCursor.x; i < fCursor.x + num; i++) {
                        line->cells[i].character = kSpaceChar;
                        line->cells[i].attributes = line->cells[fCursor.x - 1].attributes;
                }
                line->attributes = fAttributes;

                _Invalidate(fCursor.y, fCursor.y);
        }
}


void
BasicTerminalBuffer::EraseCharsFrom(int32 first, int32 numChars)
{
        TerminalLine* line = _LineAt(fCursor.y);

        int32 end = min_c(first + numChars, fWidth);
        for (int32 i = first; i < end; i++)
                line->cells[i].attributes = fAttributes;

        line->attributes = fAttributes;

        fSoftWrappedCursor = false;

        end = min_c(first + numChars, line->length);
        if (first > 0 && line->cells[first - 1].attributes.IsWidth())
                first--;
        if (end > 0 && line->cells[end - 1].attributes.IsWidth())
                end++;

        for (int32 i = first; i < end; i++) {
                line->cells[i].character = kSpaceChar;
                line->cells[i].attributes = fAttributes;
        }

        _Invalidate(fCursor.y, fCursor.y);
}


void
BasicTerminalBuffer::EraseAbove()
{
        // Clear the preceding lines.
        if (fCursor.y > 0)
                _ClearLines(0, fCursor.y - 1);

        fSoftWrappedCursor = false;

        // Delete the chars on the cursor line before (and including) the cursor.
        TerminalLine* line = _LineAt(fCursor.y);
        if (fCursor.x < line->length) {
                int32 to = fCursor.x;
                if (line->cells[fCursor.x].attributes.IsWidth())
                        to++;
                for (int32 i = 0; i <= to; i++) {
                        line->cells[i].attributes = fAttributes;
                        line->cells[i].character = kSpaceChar;
                }
        } else
                line->Clear(fAttributes, fWidth);

        _Invalidate(fCursor.y, fCursor.y);
}


void
BasicTerminalBuffer::EraseBelow()
{
        fSoftWrappedCursor = false;

        // Clear the following lines.
        if (fCursor.y < fHeight - 1)
                _ClearLines(fCursor.y + 1, fHeight - 1);

        // Delete the chars on the cursor line after (and including) the cursor.
        DeleteColumns();
}


void
BasicTerminalBuffer::EraseAll()
{
        fSoftWrappedCursor = false;
        _Scroll(0, fHeight - 1, fHeight);
}


void
BasicTerminalBuffer::EraseScrollback()
{
        fSoftWrappedCursor = false;

        // Clear the history
        if (fHistory != NULL)
                fHistory->Clear();

        // Update the scrollbars
        _Invalidate(0, 0);
}


void
BasicTerminalBuffer::DeleteChars(int32 numChars)
{
        fSoftWrappedCursor = false;

        TerminalLine* line = _LineAt(fCursor.y);
        if (fCursor.x < line->length) {
                if (fCursor.x + numChars < line->length) {
                        int32 left = line->length - fCursor.x - numChars;
                        memmove(line->cells + fCursor.x, line->cells + fCursor.x + numChars,
                                left * sizeof(TerminalCell));
                        line->length = fCursor.x + left;
                        // process BCE on freed tail cells
                        for (int i = 0; i < numChars; i++)
                                line->cells[fCursor.x + left + i].attributes = fAttributes;
                } else {
                        // process BCE on freed tail cells
                        for (int i = 0; i < line->length - fCursor.x; i++)
                                line->cells[fCursor.x + i].attributes = fAttributes;
                        // remove all remaining chars
                        line->length = fCursor.x;
                }

                _Invalidate(fCursor.y, fCursor.y);
        }
}


void
BasicTerminalBuffer::DeleteColumnsFrom(int32 first)
{
        fSoftWrappedCursor = false;

        TerminalLine* line = _LineAt(fCursor.y);

        for (int32 i = first; i < fWidth; i++)
                line->cells[i].attributes = fAttributes;

        if (first <= line->length) {
                line->length = first;
                line->attributes = fAttributes;
        }
        _Invalidate(fCursor.y, fCursor.y);
}


void
BasicTerminalBuffer::DeleteLines(int32 numLines)
{
        if (fCursor.y >= fScrollTop && fCursor.y <= fScrollBottom) {
                fSoftWrappedCursor = false;
                _Scroll(fCursor.y, fScrollBottom, numLines);
        }
}


void
BasicTerminalBuffer::SaveCursor()
{
        fSavedCursors.push(fCursor);
}


void
BasicTerminalBuffer::RestoreCursor()
{
        if (fSavedCursors.size() == 0)
                return;

        _SetCursor(fSavedCursors.top().x, fSavedCursors.top().y, true);
        fSavedCursors.pop();
}


void
BasicTerminalBuffer::SetScrollRegion(int32 top, int32 bottom)
{
        fScrollTop = restrict_value(top, 0, fHeight - 1);
        fScrollBottom = restrict_value(bottom, fScrollTop, fHeight - 1);

        // also sets the cursor position
        _SetCursor(0, 0, false);
}


void
BasicTerminalBuffer::SetOriginMode(bool enabled)
{
        fOriginMode = enabled;
        _SetCursor(0, 0, false);
}


void
BasicTerminalBuffer::SaveOriginMode()
{
        fSavedOriginMode = fOriginMode;
}


void
BasicTerminalBuffer::RestoreOriginMode()
{
        fOriginMode = fSavedOriginMode;
}


void
BasicTerminalBuffer::SetTabStop(int32 x)
{
        x = restrict_value(x, 0, fWidth - 1);
        fTabStops[x] = true;
}


void
BasicTerminalBuffer::ClearTabStop(int32 x)
{
        x = restrict_value(x, 0, fWidth - 1);
        fTabStops[x] = false;
}


void
BasicTerminalBuffer::ClearAllTabStops()
{
        for (int32 i = 0; i < fWidth; i++)
                fTabStops[i] = false;
}


void
BasicTerminalBuffer::NotifyListener()
{
        // Implemented by derived classes.
}


// #pragma mark - private methods


void
BasicTerminalBuffer::_SetCursor(int32 x, int32 y, bool absolute)
{
//debug_printf("BasicTerminalBuffer::_SetCursor(%d, %d)\n", x, y);
        fSoftWrappedCursor = false;

        x = restrict_value(x, 0, fWidth - 1);
        if (fOriginMode && !absolute) {
                y += fScrollTop;
                y = restrict_value(y, fScrollTop, fScrollBottom);
        } else {
                y = restrict_value(y, 0, fHeight - 1);
        }

        if (x != fCursor.x || y != fCursor.y) {
                fCursor.x = x;
                fCursor.y = y;
                _CursorChanged();
        }
}


void
BasicTerminalBuffer::_InvalidateAll()
{
        fDirtyInfo.invalidateAll = true;

        if (!fDirtyInfo.messageSent) {
                NotifyListener();
                fDirtyInfo.messageSent = true;
        }
}


/* static */ TerminalLine**
BasicTerminalBuffer::_AllocateLines(int32 width, int32 count)
{
        TerminalLine** lines = (TerminalLine**)malloc(sizeof(TerminalLine*) * count);
        if (lines == NULL)
                return NULL;

        for (int32 i = 0; i < count; i++) {
                const int32 size = sizeof(TerminalLine)
                        + sizeof(TerminalCell) * (width - 1);
                lines[i] = (TerminalLine*)malloc(size);
                if (lines[i] == NULL) {
                        _FreeLines(lines, i);
                        return NULL;
                }
                lines[i]->Clear(width);
        }

        return lines;
}


/* static */ void
BasicTerminalBuffer::_FreeLines(TerminalLine** lines, int32 count)
{
        if (lines != NULL) {
                for (int32 i = 0; i < count; i++)
                        free(lines[i]);

                free(lines);
        }
}


void
BasicTerminalBuffer::_ClearLines(int32 first, int32 last)
{
        int32 firstCleared = -1;
        int32 lastCleared = -1;

        for (int32 i = first; i <= last; i++) {
                TerminalLine* line = _LineAt(i);
                if (line->length > 0) {
                        if (firstCleared == -1)
                                firstCleared = i;
                        lastCleared = i;
                }

                line->Clear(fAttributes, fWidth);
        }

        if (firstCleared >= 0)
                _Invalidate(firstCleared, lastCleared);
}


status_t
BasicTerminalBuffer::_ResizeHistory(int32 width, int32 historyCapacity)
{
        if (width == fWidth && historyCapacity == HistoryCapacity())
                return B_OK;

        if (historyCapacity <= 0) {
                // new history capacity is 0 -- delete the old history object
                delete fHistory;
                fHistory = NULL;

                return B_OK;
        }

        HistoryBuffer* history = new(std::nothrow) HistoryBuffer;
        if (history == NULL)
                return B_NO_MEMORY;

        status_t error = history->Init(width, historyCapacity);
        if (error != B_OK) {
                delete history;
                return error;
        }

        // Transfer the lines from the old history to the new one.
        if (fHistory != NULL) {
                int32 historySize = min_c(HistorySize(), historyCapacity);
                TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
                for (int32 i = historySize - 1; i >= 0; i--) {
                        TerminalLine* line = fHistory->GetTerminalLineAt(i, lineBuffer);
                        if (line->length > width)
                                _TruncateLine(line, width);
                        history->AddLine(line);
                }
        }

        delete fHistory;
        fHistory = history;

        return B_OK;
}


status_t
BasicTerminalBuffer::_ResizeSimple(int32 width, int32 height,
        int32 historyCapacity)
{
//debug_printf("BasicTerminalBuffer::_ResizeSimple(): (%ld, %ld) -> "
//"(%ld, %ld)\n", fWidth, fHeight, width, height);
        if (width == fWidth && height == fHeight)
                return B_OK;

        if (width != fWidth || historyCapacity != HistoryCapacity()) {
                status_t error = _ResizeHistory(width, historyCapacity);
                if (error != B_OK)
                        return error;
        }

        TerminalLine** lines = _AllocateLines(width, height);
        if (lines == NULL)
                return B_NO_MEMORY;
                // NOTE: If width or history capacity changed, the object will be in
                // an invalid state, since the history will already use the new values.

        int32 endLine = min_c(fHeight, height);
        int32 firstLine = 0;

        if (height < fHeight) {
                if (endLine <= fCursor.y) {
                        endLine = fCursor.y + 1;
                        firstLine = endLine - height;
                }

                // push the first lines to the history
                if (fHistory != NULL) {
                        for (int32 i = 0; i < firstLine; i++) {
                                TerminalLine* line = _LineAt(i);
                                if (width < fWidth)
                                        _TruncateLine(line, width);
                                fHistory->AddLine(line);
                        }
                }
        }

        // copy the lines we keep
        for (int32 i = firstLine; i < endLine; i++) {
                TerminalLine* sourceLine = _LineAt(i);
                TerminalLine* destLine = lines[i - firstLine];
                if (width < fWidth)
                        _TruncateLine(sourceLine, width);
                memcpy(destLine, sourceLine, (int32)sizeof(TerminalLine)
                        + (sourceLine->length - 1) * (int32)sizeof(TerminalCell));
        }

        // clear the remaining lines
        for (int32 i = endLine - firstLine; i < height; i++)
                lines[i]->Clear(fAttributes, width);

        _FreeLines(fScreen, fHeight);
        fScreen = lines;

        if (fWidth != width) {
                status_t error = _ResetTabStops(width);
                if (error != B_OK)
                        return error;
        }

        fWidth = width;
        fHeight = height;

        fScrollTop = 0;
        fScrollBottom = fHeight - 1;
        fOriginMode = fSavedOriginMode = false;

        fScreenOffset = 0;

        if (fCursor.x > width)
                fCursor.x = width;
        fCursor.y -= firstLine;
        fSoftWrappedCursor = false;

        return B_OK;
}


status_t
BasicTerminalBuffer::_ResizeRewrap(int32 width, int32 height,
        int32 historyCapacity)
{
//debug_printf("BasicTerminalBuffer::_ResizeRewrap(): (%ld, %ld, history: %ld) -> "
//"(%ld, %ld, history: %ld)\n", fWidth, fHeight, HistoryCapacity(), width, height,
//historyCapacity);

        // The width stays the same. _ResizeSimple() does exactly what we need.
        if (width == fWidth)
                return _ResizeSimple(width, height, historyCapacity);

        // The width changes. We have to allocate a new line array, a new history
        // and re-wrap all lines.

        TerminalLine** screen = _AllocateLines(width, height);
        if (screen == NULL)
                return B_NO_MEMORY;

        HistoryBuffer* history = NULL;

        if (historyCapacity > 0) {
                history = new(std::nothrow) HistoryBuffer;
                if (history == NULL) {
                        _FreeLines(screen, height);
                        return B_NO_MEMORY;
                }

                status_t error = history->Init(width, historyCapacity);
                if (error != B_OK) {
                        _FreeLines(screen, height);
                        delete history;
                        return error;
                }
        }

        int32 historySize = HistorySize();
        int32 totalLines = historySize + fHeight;

        // re-wrap
        TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
        TermPos cursor;
        int32 destIndex = 0;
        int32 sourceIndex = 0;
        int32 sourceX = 0;
        int32 destTotalLines = 0;
        int32 destScreenOffset = 0;
        int32 maxDestTotalLines = INT_MAX;
        bool newDestLine = true;
        bool cursorSeen = false;
        TerminalLine* sourceLine = _HistoryLineAt(-historySize, lineBuffer);

        while (sourceIndex < totalLines) {
                TerminalLine* destLine = screen[destIndex];

                if (newDestLine) {
                        // Clear a new dest line before using it. If we're about to
                        // overwrite an previously written line, we push it to the
                        // history first, though.
                        if (history != NULL && destTotalLines >= height)
                                history->AddLine(screen[destIndex]);
                        destLine->Clear(fAttributes, width);
                        newDestLine = false;
                }

                int32 sourceLeft = sourceLine->length - sourceX;
                int32 destLeft = width - destLine->length;
//debug_printf("    source: %ld, left: %ld, dest: %ld, left: %ld\n",
//sourceIndex, sourceLeft, destIndex, destLeft);

                if (sourceIndex == historySize && sourceX == 0) {
                        destScreenOffset = destTotalLines;
                        if (destLeft == 0 && sourceLeft > 0)
                                destScreenOffset++;
                        maxDestTotalLines = destScreenOffset + height;
//debug_printf("      destScreenOffset: %ld\n", destScreenOffset);
                }

                int32 toCopy = min_c(sourceLeft, destLeft);
                // If the last cell to copy is the first cell of a
                // full-width char, don't copy it yet.
                if (toCopy > 0 && sourceLine->cells[sourceX + toCopy - 1].attributes.IsWidth()) {
//debug_printf("      -> last char is full-width -- don't copy it\n");
                        toCopy--;
                }

                // translate the cursor position
                if (fCursor.y + historySize == sourceIndex
                        && fCursor.x >= sourceX
                        && (fCursor.x < sourceX + toCopy
                                || (destLeft >= sourceLeft
                                        && sourceX + sourceLeft <= fCursor.x))) {
                        cursor.x = destLine->length + fCursor.x - sourceX;
                        cursor.y = destTotalLines;

                        if (cursor.x >= width) {
                                // The cursor was in free space after the official end
                                // of line.
                                cursor.x = width - 1;
                        }
//debug_printf("      cursor: (%ld, %ld)\n", cursor.x, cursor.y);

                        cursorSeen = true;
                }

                if (toCopy > 0) {
                        memcpy(destLine->cells + destLine->length,
                                sourceLine->cells + sourceX, toCopy * sizeof(TerminalCell));
                        destLine->length += toCopy;
                }

                destLine->attributes = sourceLine->attributes;

                bool nextDestLine = false;
                if (toCopy == sourceLeft) {
                        if (!sourceLine->softBreak)
                                nextDestLine = true;
                        sourceIndex++;
                        sourceX = 0;
                        sourceLine = _HistoryLineAt(sourceIndex - historySize,
                                lineBuffer);
                } else {
                        destLine->softBreak = true;
                        nextDestLine = true;
                        sourceX += toCopy;
                }

                if (nextDestLine) {
                        destIndex = (destIndex + 1) % height;
                        destTotalLines++;
                        newDestLine = true;
                        if (cursorSeen && destTotalLines >= maxDestTotalLines)
                                break;
                }
        }

        // If the last source line had a soft break, the last dest line
        // won't have been counted yet.
        if (!newDestLine) {
                destIndex = (destIndex + 1) % height;
                destTotalLines++;
        }

//debug_printf("  total lines: %ld -> %ld\n", totalLines, destTotalLines);

        if (destTotalLines - destScreenOffset > height)
                destScreenOffset = destTotalLines - height;

        cursor.y -= destScreenOffset;

        // When there are less lines (starting with the screen offset) than
        // there's room in the screen, clear the remaining screen lines.
        for (int32 i = destTotalLines; i < destScreenOffset + height; i++) {
                // Move the line we're going to clear to the history, if that's a
                // line we've written earlier.
                TerminalLine* line = screen[i % height];
                if (history != NULL && i >= height)
                        history->AddLine(line);
                line->Clear(fAttributes, width);
        }

        // Update the values
        _FreeLines(fScreen, fHeight);
        delete fHistory;

        fScreen = screen;
        fHistory = history;

        if (fWidth != width) {
                status_t error = _ResetTabStops(width);
                if (error != B_OK)
                        return error;
        }

//debug_printf("  cursor: (%ld, %ld) -> (%ld, %ld)\n", fCursor.x, fCursor.y,
//cursor.x, cursor.y);
        fCursor.x = cursor.x;
        fCursor.y = cursor.y;
        fSoftWrappedCursor = false;
//debug_printf("  screen offset: %ld -> %ld\n", fScreenOffset, destScreenOffset % height);
        fScreenOffset = destScreenOffset % height;
//debug_printf("  height %ld -> %ld\n", fHeight, height);
//debug_printf("  width %ld -> %ld\n", fWidth, width);
        fHeight = height;
        fWidth = width;

        fScrollTop = 0;
        fScrollBottom = fHeight - 1;
        fOriginMode = fSavedOriginMode = false;

        return B_OK;
}


status_t
BasicTerminalBuffer::_ResetTabStops(int32 width)
{
        if (fTabStops != NULL)
                delete[] fTabStops;

        fTabStops = new(std::nothrow) bool[width];
        if (fTabStops == NULL)
                return B_NO_MEMORY;

        for (int32 i = 0; i < width; i++)
                fTabStops[i] = (i % TAB_WIDTH) == 0;
        return B_OK;
}


void
BasicTerminalBuffer::_Scroll(int32 top, int32 bottom, int32 numLines)
{
        if (numLines == 0)
                return;

        if (numLines > 0) {
                // scroll text up
                if (top == 0) {
                        // The lines scrolled out of the screen range are transferred to
                        // the history.

                        // add the lines to the history
                        if (fHistory != NULL) {
                                int32 toHistory = min_c(numLines, bottom - top + 1);
                                for (int32 i = 0; i < toHistory; i++)
                                        fHistory->AddLine(_LineAt(i));

                                if (toHistory < numLines)
                                        fHistory->AddEmptyLines(numLines - toHistory);
                        }

                        if (numLines >= bottom - top + 1) {
                                // all lines are scrolled out of range -- just clear them
                                _ClearLines(top, bottom);
                        } else if (bottom == fHeight - 1) {
                                // full screen scroll -- update the screen offset and clear new
                                // lines
                                fScreenOffset = (fScreenOffset + numLines) % fHeight;
                                for (int32 i = bottom - numLines + 1; i <= bottom; i++)
                                        _LineAt(i)->Clear(fAttributes, fWidth);
                        } else {
                                // Partial screen scroll. We move the screen offset anyway, but
                                // have to move the unscrolled lines to their new location.
                                // TODO: It may be more efficient to actually move the scrolled
                                // lines only (might depend on the number of scrolled/unscrolled
                                // lines).
                                for (int32 i = fHeight - 1; i > bottom; i--) {
                                        std::swap(fScreen[_LineIndex(i)],
                                                fScreen[_LineIndex(i + numLines)]);
                                }

                                // update the screen offset and clear the new lines
                                fScreenOffset = (fScreenOffset + numLines) % fHeight;
                                for (int32 i = bottom - numLines + 1; i <= bottom; i++)
                                        _LineAt(i)->Clear(fAttributes, fWidth);
                        }

                        // scroll/extend dirty range

                        if (fDirtyInfo.dirtyTop != INT_MAX) {
                                // If the top or bottom of the dirty region are above the
                                // bottom of the scroll region, we have to scroll them up.
                                if (fDirtyInfo.dirtyTop <= bottom) {
                                        fDirtyInfo.dirtyTop -= numLines;
                                        if (fDirtyInfo.dirtyBottom <= bottom)
                                                fDirtyInfo.dirtyBottom -= numLines;
                                }

                                // numLines above the bottom become dirty
                                _Invalidate(bottom - numLines + 1, bottom);
                        }

                        fDirtyInfo.linesScrolled += numLines;

                        // invalidate new empty lines
                        _Invalidate(bottom + 1 - numLines, bottom);

                        // In case only part of the screen was scrolled, we invalidate also
                        // the lines below the scroll region. Those remain unchanged, but
                        // we can't convey that they have not been scrolled via
                        // TerminalBufferDirtyInfo. So we need to force the view to sync
                        // them again.
                        if (bottom < fHeight - 1)
                                _Invalidate(bottom + 1, fHeight - 1);
                } else if (numLines >= bottom - top + 1) {
                        // all lines are completely scrolled out of range -- just clear
                        // them
                        _ClearLines(top, bottom);
                } else {
                        // partial scroll -- clear the lines scrolled out of range and move
                        // the other ones
                        for (int32 i = top + numLines; i <= bottom; i++) {
                                int32 lineToDrop = _LineIndex(i - numLines);
                                int32 lineToKeep = _LineIndex(i);
                                fScreen[lineToDrop]->Clear(fAttributes, fWidth);
                                std::swap(fScreen[lineToDrop], fScreen[lineToKeep]);
                        }
                        // clear any lines between the two swapped ranges above
                        for (int32 i = bottom - numLines + 1; i < top + numLines; i++)
                                _LineAt(i)->Clear(fAttributes, fWidth);

                        _Invalidate(top, bottom);
                }
        } else {
                // scroll text down
                numLines = -numLines;

                if (numLines >= bottom - top + 1) {
                        // all lines are completely scrolled out of range -- just clear
                        // them
                        _ClearLines(top, bottom);
                } else {
                        // partial scroll -- clear the lines scrolled out of range and move
                        // the other ones
// TODO: When scrolling the whole screen, we could just update fScreenOffset and
// clear the respective lines.
                        for (int32 i = bottom - numLines; i >= top; i--) {
                                int32 lineToKeep = _LineIndex(i);
                                int32 lineToDrop = _LineIndex(i + numLines);
                                fScreen[lineToDrop]->Clear(fAttributes, fWidth);
                                std::swap(fScreen[lineToDrop], fScreen[lineToKeep]);
                        }
                        // clear any lines between the two swapped ranges above
                        for (int32 i = bottom - numLines + 1; i < top + numLines; i++)
                                _LineAt(i)->Clear(fAttributes, fWidth);

                        _Invalidate(top, bottom);
                }
        }
}


void
BasicTerminalBuffer::_SoftBreakLine()
{
        TerminalLine* line = _LineAt(fCursor.y);
        line->softBreak = true;

        fCursor.x = 0;
        if (fCursor.y == fScrollBottom)
                _Scroll(fScrollTop, fScrollBottom, 1);
        else
                fCursor.y++;
}


void
BasicTerminalBuffer::_PadLineToCursor()
{
        TerminalLine* line = _LineAt(fCursor.y);
        if (line->length < fCursor.x)
                for (int32 i = line->length; i < fCursor.x; i++)
                        line->cells[i].character = kSpaceChar;
}


/*static*/ void
BasicTerminalBuffer::_TruncateLine(TerminalLine* line, int32 length)
{
        if (line->length <= length)
                return;

        if (length > 0 && line->cells[length - 1].attributes.IsWidth())
                length--;

        line->length = length;
}


void
BasicTerminalBuffer::_InsertGap(int32 width)
{
// ASSERT(fCursor.x + width <= fWidth)
        TerminalLine* line = _LineAt(fCursor.y);

        int32 toMove = min_c(line->length - fCursor.x, fWidth - fCursor.x - width);
        if (toMove > 0) {
                memmove(line->cells + fCursor.x + width,
                        line->cells + fCursor.x, toMove * sizeof(TerminalCell));
        }

        line->length = min_c(line->length + width, fWidth);
}


/*!     \a endColumn is not inclusive.
*/
TerminalLine*
BasicTerminalBuffer::_GetPartialLineString(BString& string, int32 row,
        int32 startColumn, int32 endColumn) const
{
        TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
        TerminalLine* line = _HistoryLineAt(row, lineBuffer);
        if (line == NULL)
                return NULL;

        if (endColumn > line->length)
                endColumn = line->length;

        for (int32 x = startColumn; x < endColumn; x++) {
                const TerminalCell& cell = line->cells[x];
                string.Append(cell.character.bytes, cell.character.ByteCount());

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

        return line;
}


/*!     Decrement \a pos and return the char at that location.
*/
bool
BasicTerminalBuffer::_PreviousChar(TermPos& pos, UTF8Char& c) const
{
        pos.x--;

        TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
        TerminalLine* line = _HistoryLineAt(pos.y, lineBuffer);

        while (true) {
                if (pos.x < 0) {
                        pos.y--;
                        line = _HistoryLineAt(pos.y, lineBuffer);
                        if (line == NULL)
                                return false;

                        pos.x = line->length;
                        if (line->softBreak) {
                                pos.x--;
                        } else {
                                c = '\n';
                                return true;
                        }
                } else {
                        c = line->cells[pos.x].character;
                        return true;
                }
        }
}


/*!     Return the char at \a pos and increment it.
*/
bool
BasicTerminalBuffer::_NextChar(TermPos& pos, UTF8Char& c) const
{
        TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
        TerminalLine* line = _HistoryLineAt(pos.y, lineBuffer);
        if (line == NULL)
                return false;

        if (pos.x >= line->length) {
                c = '\n';
                pos.x = 0;
                pos.y++;
                return true;
        }

        c = line->cells[pos.x].character;

        pos.x++;
        while (line != NULL && pos.x >= line->length && line->softBreak) {
                pos.x = 0;
                pos.y++;
                line = _HistoryLineAt(pos.y, lineBuffer);
        }

        return true;
}


bool
BasicTerminalBuffer::_PreviousLinePos(TerminalLine* lineBuffer,
        TerminalLine*& line, TermPos& pos) const
{
        if (--pos.x < 0) {
                // Hit the beginning of the line -- continue at the end of the
                // previous line, if it soft-breaks.
                pos.y--;
                if ((line = _HistoryLineAt(pos.y, lineBuffer)) == NULL
                                || !line->softBreak || line->length == 0) {
                        return false;
                }
                pos.x = line->length - 1;
        }
        if (pos.x > 0 && line->cells[pos.x - 1].attributes.IsWidth())
                pos.x--;

        return true;
}


bool
BasicTerminalBuffer::_NormalizeLinePos(TerminalLine* lineBuffer,
        TerminalLine*& line, TermPos& pos) const
{
        if (pos.x < line->length)
                return true;

        // Hit the end of the line -- if it soft-breaks continue with the
        // next line.
        if (!line->softBreak)
                return false;

        pos.y++;
        pos.x = 0;
        line = _HistoryLineAt(pos.y, lineBuffer);
        return line != NULL;
}


#ifdef USE_DEBUG_SNAPSHOTS

void
BasicTerminalBuffer::MakeLinesSnapshots(time_t timeStamp, const char* fileName)
{
        BString str("/var/log/");
        struct tm* ts = gmtime(&timeStamp);
        str << ts->tm_hour << ts->tm_min << ts->tm_sec;
        str << fileName;
        FILE* fileOut = fopen(str.String(), "w");

        bool dumpHistory = false;
        do {
                if (dumpHistory && fHistory == NULL) {
                        fprintf(fileOut, "> History is empty <\n");
                        break;
                }

                int countLines = dumpHistory ? fHistory->Size() : fHeight;
                fprintf(fileOut, "> %s lines dump begin <\n",
                                        dumpHistory ? "History" : "Terminal");

                TerminalLine* lineBuffer = ALLOC_LINE_ON_STACK(fWidth);
                for (int i = 0; i < countLines; i++) {
                        TerminalLine* line = dumpHistory
                                ? fHistory->GetTerminalLineAt(i, lineBuffer)
                                        : fScreen[_LineIndex(i)];

                        if (line == NULL) {
                                fprintf(fileOut, "line: %d is NULL!!!\n", i);
                                continue;
                        }

                        fprintf(fileOut, "%02" B_PRId16 ":%02" B_PRId16 ":%08" B_PRIx32 ":\n",
                                        i, line->length, line->attributes.state);
                        for (int j = 0; j < line->length; j++)
                                if (line->cells[j].character.bytes[0] != 0)
                                        fwrite(line->cells[j].character.bytes, 1,
                                                line->cells[j].character.ByteCount(), fileOut);

                        fprintf(fileOut, "\n");
                        for (int s = 28; s >= 0; s -= 4) {
                                for (int j = 0; j < fWidth; j++)
                                        fprintf(fileOut, "%01" B_PRIx32,
                                                (line->cells[j].attributes.state >> s) & 0x0F);

                                fprintf(fileOut, "\n");
                        }

                        fprintf(fileOut, "\n");
                }

                fprintf(fileOut, "> %s lines dump finished <\n",
                                        dumpHistory ? "History" : "Terminal");

                dumpHistory = !dumpHistory;
        } while (dumpHistory);

        fclose(fileOut);
}


void
BasicTerminalBuffer::StartStopDebugCapture()
{
        if (fCaptureFile >= 0) {
                close(fCaptureFile);
                fCaptureFile = -1;
                return;
        }

        time_t timeStamp = time(NULL);
        BString str("/var/log/");
        struct tm* ts = gmtime(&timeStamp);
        str << ts->tm_hour << ts->tm_min << ts->tm_sec;
        str << ".Capture.log";
        fCaptureFile = open(str.String(), O_CREAT | O_WRONLY,
                        S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH);
}


void
BasicTerminalBuffer::CaptureChar(char ch)
{
        if (fCaptureFile >= 0)
                write(fCaptureFile, &ch, 1);
}


void
BasicTerminalBuffer::InsertLastChar()
{
        InsertChar(fLast);
}

#endif