root/src/apps/terminal/TerminalBuffer.cpp
/*
 * Copyright 2013-2023, 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
 *              Simon South, simon@simonsouth.net
 *              Siarzhuk Zharski, zharik@gmx.li
 */

#include "TerminalBuffer.h"

#include <algorithm>

#include <Message.h>

#include "Colors.h"
#include "TermApp.h"
#include "TermConst.h"


// #pragma mark - public methods


TerminalBuffer::TerminalBuffer()
        :
        BLocker("terminal buffer"),
        fEncoding(M_UTF8),
        fAlternateScreen(NULL),
        fAlternateHistory(NULL),
        fAlternateScreenOffset(0),
        fAlternateAttributes(),
        fColorsPalette(NULL),
        fListenerValid(false),
        fMode(MODE_INTERPRET_META_KEY | MODE_META_KEY_SENDS_ESCAPE | MODE_CURSOR_BLINKING),
        fCursorStyle(BLOCK_CURSOR),
        fNextOSCRef(1)
{
}


TerminalBuffer::~TerminalBuffer()
{
        free(fAlternateScreen);
        delete fAlternateHistory;
        delete[] fColorsPalette;

        HyperLinkRefMap::Iterator iterator = fHyperLinkForRef.GetIterator();
        while (iterator.HasNext()) {
                HyperLinkRefMap::Entry entry = iterator.Next();
                delete entry.value;
        }
        fHyperLinkForID.Clear();
        fHyperLinkForRef.Clear();
}


status_t
TerminalBuffer::Init(int32 width, int32 height, int32 historySize)
{
        if (BLocker::InitCheck() < 0)
                return BLocker::InitCheck();

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

        for (int32 i = 0; i < height; i++)
                fAlternateScreen[i]->Clear();

        fColorsPalette = new(std::nothrow) rgb_color[kTermColorCount];
        if (fColorsPalette == NULL)
                return B_NO_MEMORY;

        for (uint i = 0; i < kTermColorCount; i++)
                fColorsPalette[i] = TermApp::DefaultPalette()[i];

        return BasicTerminalBuffer::Init(width, height, historySize);
}


void
TerminalBuffer::SetListener(BMessenger listener)
{
        fListener = listener;
        fListenerValid = true;
}


void
TerminalBuffer::UnsetListener()
{
        fListenerValid = false;
}


int
TerminalBuffer::Encoding() const
{
        return fEncoding;
}


bool
TerminalBuffer::IsMode(uint32 mode) const
{
        return (fMode & mode) != 0;
}


void
TerminalBuffer::SetMode(uint32 mode)
{
        fMode |= mode;
        if (fListenerValid && (mode & MODE_CURSOR_BLINKING) != 0)
                fListener.SendMessage(MSG_SET_CURSOR_STYLE);
}


void
TerminalBuffer::ResetMode(uint32 mode)
{
        fMode &= ~mode;
        if (fListenerValid && (mode & MODE_CURSOR_BLINKING) != 0)
                fListener.SendMessage(MSG_SET_CURSOR_STYLE);
}


void
TerminalBuffer::SetEncoding(int encoding)
{
        fEncoding = encoding;
}


void
TerminalBuffer::SetTitle(const char* title)
{
        if (fListenerValid) {
                BMessage message(MSG_SET_TERMINAL_TITLE);
                message.AddString("title", title);
                fListener.SendMessage(&message);
        }
}


void
TerminalBuffer::SetColors(uint8* indexes, rgb_color* colors,
                int32 count, bool dynamic)
{
        if (fListenerValid) {
                BMessage message(MSG_SET_TERMINAL_COLORS);
                message.AddInt32("count", count);
                message.AddBool("dynamic", dynamic);
                message.AddData("index", B_UINT8_TYPE,
                                        indexes, sizeof(uint8), true, count);
                message.AddData("color", B_RGB_COLOR_TYPE,
                                        colors, sizeof(rgb_color), true, count);

                for (int i = 1; i < count; i++) {
                        message.AddData("index", B_UINT8_TYPE, &indexes[i], sizeof(uint8));
                        message.AddData("color", B_RGB_COLOR_TYPE, &colors[i],
                                        sizeof(rgb_color));
                }

                fListener.SendMessage(&message);
        }
}


void
TerminalBuffer::ResetColors(uint8* indexes, int32 count, bool dynamic)
{
        if (fListenerValid) {
                BMessage message(MSG_RESET_TERMINAL_COLORS);
                message.AddInt32("count", count);
                message.AddBool("dynamic", dynamic);
                message.AddData("index", B_UINT8_TYPE,
                                        indexes, sizeof(uint8), true, count);

                for (int i = 1; i < count; i++)
                        message.AddData("index", B_UINT8_TYPE, &indexes[i], sizeof(uint8));

                fListener.SendMessage(&message);
        }
}


void
TerminalBuffer::GetColor(uint8 index)
{
        if (fListenerValid) {
                BMessage message(MSG_GET_TERMINAL_COLOR);
                message.AddUInt8("index", index);
                fListener.SendMessage(&message);
        }
}


void
TerminalBuffer::SetCursorStyle(int32 style)
{
        fCursorStyle = style;
}


void
TerminalBuffer::SetPaletteColor(uint8 index, rgb_color color)
{
        fColorsPalette[index] = color;
}


void
TerminalBuffer::NotifyQuit(int32 reason)
{
        if (fListenerValid) {
                BMessage message(MSG_QUIT_TERMNAL);
                message.AddInt32("reason", reason);
                fListener.SendMessage(&message);
        }
}


void
TerminalBuffer::NotifyListener()
{
        if (fListenerValid)
                fListener.SendMessage(MSG_TERMINAL_BUFFER_CHANGED);
}


status_t
TerminalBuffer::ResizeTo(int32 width, int32 height)
{
        int32 historyCapacity = 0;
        if (!fAlternateScreenActive)
                historyCapacity = HistoryCapacity();
        else if (fAlternateHistory != NULL)
                historyCapacity = fAlternateHistory->Capacity();

        return ResizeTo(width, height, historyCapacity);
}


status_t
TerminalBuffer::ResizeTo(int32 width, int32 height, int32 historyCapacity)
{
        // switch to the normal screen buffer first
        bool alternateScreenActive = fAlternateScreenActive;
        if (alternateScreenActive)
                _SwitchScreenBuffer();

        int32 oldWidth = fWidth;
        int32 oldHeight = fHeight;

        // Resize the normal screen buffer/history.
        status_t error = BasicTerminalBuffer::ResizeTo(width, height,
                historyCapacity);
        if (error != B_OK) {
                if (alternateScreenActive)
                        _SwitchScreenBuffer();
                return error;
        }

        // Switch to the alternate screen buffer and resize it.
        if (fAlternateScreen != NULL) {
                TermPos cursor = fCursor;
                fCursor.SetTo(0, 0);
                fWidth = oldWidth;
                fHeight = oldHeight;

                _SwitchScreenBuffer();

                error = BasicTerminalBuffer::ResizeTo(width, height, 0);

                fWidth = width;
                fHeight = height;
                fCursor = cursor;

                // Switch back.
                if (!alternateScreenActive)
                        _SwitchScreenBuffer();

                if (error != B_OK) {
                        // This sucks -- we can't do anything about it. Delete the
                        // alternate screen buffer.
                        _FreeLines(fAlternateScreen, oldHeight);
                        fAlternateScreen = NULL;
                }
        }

        return error;
}


void
TerminalBuffer::UseAlternateScreenBuffer(bool clear)
{
        if (fAlternateScreenActive || fAlternateScreen == NULL)
                return;

        _SwitchScreenBuffer();

        if (clear)
                Clear(false);

        _InvalidateAll();
}


void
TerminalBuffer::UseNormalScreenBuffer()
{
        if (!fAlternateScreenActive)
                return;

        _SwitchScreenBuffer();
        _InvalidateAll();
}


void
TerminalBuffer::_SwitchScreenBuffer()
{
        std::swap(fScreen, fAlternateScreen);
        std::swap(fHistory, fAlternateHistory);
        std::swap(fScreenOffset, fAlternateScreenOffset);
        std::swap(fAttributes, fAlternateAttributes);
        fAlternateScreenActive = !fAlternateScreenActive;
}


uint32
TerminalBuffer::PutHyperLink(const char* id, BString& uri)
{
        HyperLink* hyperLink = NULL;
        if (id != NULL)
                hyperLink = fHyperLinkForID.Get(id);
        if (hyperLink == NULL) {
                hyperLink = new (std::nothrow) HyperLink(uri, fNextOSCRef++, id);
                if (id != NULL)
                        fHyperLinkForID.Put(id, hyperLink);
                HyperLink* oldHyperLink = fHyperLinkForRef.Get(hyperLink->OSCRef());
                fHyperLinkForRef.Put(hyperLink->OSCRef(), hyperLink);
                delete oldHyperLink;
        }
        return hyperLink->OSCRef();
}


bool
TerminalBuffer::GetHyperLink(uint32 ref, HyperLink &_link)
{
        HyperLink* hyperLink = fHyperLinkForRef.Get(ref);
        if (hyperLink == NULL) {
                return false;
        }
        _link = *hyperLink;
        return true;
}