AuroraRuntime/Source/Console/ConsoleTTY/ConsoleTTY.cpp

2501 lines
67 KiB
C++

/***
Copyright (C) 2022 J Reece Wilson (a/k/a "Reece"). All rights reserved.
File: ConsoleTTY.cpp
Date: 2022-5-11
Author: Reece
***/
#define I_REALLY_NEED_WIDECHAR_PUBAPI // bc linux
#include <Source/RuntimeInternal.hpp>
#include <Source/Console/Console.hpp>
#include "ConsoleTTY.hpp"
#include <Source/Console/ConsoleStd/ConsoleStd.hpp>
#if defined(AURORA_IS_MODERNNT_DERIVED)
#include "ConsoleTTY.NT.hpp"
#endif
#if defined(AURORA_IS_POSIX_DERIVED)
#include "ConsoleTTY.Unix.hpp"
#endif
#if defined(AURORA_COMPILER_CLANG)
// warning: enumeration values 'kEnumCount' and 'kEnumInvalid' not handled in switch [-Wswitch
#pragma clang diagnostic ignored "-Wswitch"
// Yea, I don't give a shit.
#endif
#include "../ColorConvert.hpp"
namespace Aurora::Console::ConsoleTTY
{
static AuThreads::ThreadUnique_t gConsoleTTYThread;
static bool gMultiThreadTTY { true };
static bool gBuggyFastAppend { false };
#if 1
AuUInt32 GuessWidth(const AuString &referenceLine)
{
return AuUInt32(AuLocale::ConvertFromUTF8(referenceLine).size());
}
TTYConsoleField::TTYConsoleField(TTYConsole *pParent) :
pParent(pParent)
{
}
void TTYConsoleField::AddString(const AuString &input)
{
if (this->highlightStartInBytes)
{
this->Backspace();
}
if (this->noncanonicalCursorPosInBytes == this->inputField.size())
{
this->inputField += input;
}
else
{
auto itr = this->inputField.begin();
std::advance(itr, this->noncanonicalCursorPosInBytes);
this->inputField.insert(itr, input.begin(), input.end());
}
this->noncanonicalCursorPos += GuessWidth(input);
this->noncanonicalCursorPosInBytes += (int)input.size();
}
void TTYConsoleField::DoUnselectTick()
{
auto lastState = this->highlightStartInBytes.HasValue();
this->bIsSelecting = false;
AuResetMember(this->highlightEndInBytes);
AuResetMember(this->highlightEndInPos);
AuResetMember(this->highlightStartInBytes);
AuResetMember(this->highlightStart);
if (lastState)
{
this->pParent->SignalRedraw();
}
}
void TTYConsoleField::WriteBuffered(TTYConsole *pConsole, AuPair<AuUInt32, AuUInt32> pos)
{
if (this->highlightStartInBytes)
{
auto sViewZero = this->highlightStartInBytes.value() >= this->inputField.size() ?
this->inputField :
this->inputField.substr(0, this->highlightStartInBytes.value());
auto sViewOne = this->highlightStartInBytes.value() >= this->inputField.size() ? "" :
this->highlightEndInBytes.value() > this->inputField.size() ? "" :
this->inputField.substr(this->highlightStartInBytes.value(),
this->highlightEndInBytes.value() - this->highlightStartInBytes.value());
auto sViewTwo = this->highlightEndInBytes.value() < this->inputField.size() ?
this->inputField.substr(this->highlightEndInBytes.value()) :
"";
pConsole->WriteBufferedEx({ pos.first, pos.second }, AuConsole::EAnsiColor::eWhite, sViewZero);
pConsole->WriteBufferedEx({ pos.first + GuessWidth(sViewZero), pos.second }, AuConsole::EAnsiColor::eGreen, sViewOne);
pConsole->WriteBufferedEx({ pos.first + GuessWidth(sViewZero) + GuessWidth(sViewOne), pos.second }, AuConsole::EAnsiColor::eWhite, sViewTwo);
}
else
{
pConsole->WriteBuffered(pos, this->inputField);
}
}
void TTYConsoleField::Clear()
{
AuResetMember(this->highlightStartInBytes);
AuResetMember(this->highlightEndInBytes);
AuResetMember(this->highlightEndInPos);
AuResetMember(this->highlightStart);
AuResetMember(this->highlightChars);
this->inputField.clear();
this->noncanonicalCursorPos = 0;
this->noncanonicalCursorPosInBytes = 0;
}
bool TTYConsoleField::Backspace()
{
if (this->highlightStartInBytes)
{
int idx = highlightStartInBytes.value();
int endCnt = highlightEndInBytes.value() - idx;
auto sViewZero = this->inputField.substr(0, this->highlightStartInBytes.value());
auto sViewTwo = this->highlightEndInBytes.value() < this->inputField.size() ?
this->inputField.substr(this->highlightEndInBytes.value()) :
"";
this->inputField = sViewZero + sViewTwo;
this->noncanonicalCursorPos = this->highlightStart.value();
this->noncanonicalCursorPosInBytes = this->highlightStartInBytes.value();
DoUnselectTick();
}
else
{
if (this->noncanonicalCursorPosInBytes <= 0)
{
return false;
}
int idx = AuLocale::Encoding::CountUTF8Length({ this->inputField.data(), AuUInt(this->noncanonicalCursorPosInBytes - 1) }, true);
int endCnt = AuLocale::Encoding::CountUTF8Length({ this->inputField.data() + idx, this->inputField.size() - idx }, true);
if (!endCnt)
{
this->inputField.resize(idx);
}
else
{
auto suffix = inputField.substr(noncanonicalCursorPosInBytes);
this->inputField.resize(idx);
this->inputField += suffix;
}
this->noncanonicalCursorPos--;
this->noncanonicalCursorPosInBytes = idx;
}
return true;
}
void TTYConsoleField::DirectionalArrow(int iDirection, bool bShift, bool bControl)
{
if (this->bIsSelecting)
{
if (this->iLastDirection != iDirection)
{
this->iLastDirection = iDirection;
DoUnselectTick();
}
}
else if (bShift)
{
this->iLastDirection = iDirection;
}
this->bIsSelecting = bShift;
auto noncanonicalCursorPos = this->highlightStart ? this->highlightStart.value() : this->noncanonicalCursorPos;
auto noncanonicalCursorPosInBytes = this->highlightStart ? this->highlightStartInBytes.value() : this->noncanonicalCursorPosInBytes;
auto getCurrentChar = [=](int iOffset) -> int
{
auto uIndex = this->noncanonicalCursorPosInBytes + iOffset;
if (uIndex >= this->inputField.size()) return 4;
if (!(bool)isascii(this->inputField[uIndex]))
{
return 2;
}
if ((bool)::isupper(this->inputField[uIndex]))
{
return 1;
}
if (this->inputField[uIndex] == ' ' ||
this->inputField[uIndex] == '(' ||
this->inputField[uIndex] == ')' ||
this->inputField[uIndex] == ',')
{
return 0;
}
if ((bool)::isalpha(this->inputField[uIndex]))
{
return 6;
}
return 5;
};
bool bTickAgain {};
do
{
int iCurState;
int iNextState;
if (iDirection == 1)
{
iCurState = getCurrentChar(0);
Right();
iNextState = getCurrentChar(0);
}
else
{
iCurState = getCurrentChar(-1);
Left();
iNextState = getCurrentChar(-1);
}
if (bControl)
{
bTickAgain = iNextState == iCurState;
bTickAgain &= (((bool)this->noncanonicalCursorPos) &&
(this->noncanonicalCursorPosInBytes < this->inputField.size()));
}
}
while (bTickAgain);
if (this->bIsSelecting)
{
auto uEnd1 = AuMax(this->noncanonicalCursorPos, noncanonicalCursorPos);
auto uEnd2 = AuMax(this->noncanonicalCursorPosInBytes, noncanonicalCursorPosInBytes);
if (this->highlightEndInPos)
{
uEnd1 = AuMax(uEnd1, this->highlightEndInPos.value());
}
if (this->highlightEndInBytes)
{
uEnd2 = AuMax(uEnd1, this->highlightEndInBytes.value());
}
auto uStart1 = AuMin(this->noncanonicalCursorPos, noncanonicalCursorPos);
auto uStart2 = AuMin(this->noncanonicalCursorPosInBytes, noncanonicalCursorPosInBytes);
this->highlightStartInBytes = uStart2;
this->highlightEndInBytes = uEnd2;
this->highlightEndInPos = uEnd1;
this->highlightStart = uStart1;
this->highlightChars = AuLocale::Encoding::CountUTF8Length({ this->inputField.data() + uStart2, (AuUInt)uEnd2 - uStart2 }, false);
this->pParent->SignalRedraw();
}
else
{
DoUnselectTick();
}
}
void TTYConsoleField::Left()
{
if (this->noncanonicalCursorPosInBytes == 0)
{
return;
}
int idx = AuLocale::Encoding::CountUTF8Length({ this->inputField.data(), AuUInt(this->noncanonicalCursorPosInBytes - 1) },
true);
this->noncanonicalCursorPos--;
this->noncanonicalCursorPosInBytes = idx;
}
void TTYConsoleField::Right()
{
if (this->inputField.size() <= this->noncanonicalCursorPosInBytes)
{
return;
}
auto uAddend = AuLocale::Encoding::IterateUTF8({ &this->inputField[this->noncanonicalCursorPosInBytes],
this->inputField.size() - this->noncanonicalCursorPosInBytes });
this->noncanonicalCursorPos++;
this->noncanonicalCursorPosInBytes += uAddend;
}
TTYConsole::TTYConsole() :
inputField(this)
{
}
bool TTYConsole::IsShowingHintLine()
{
return false;
//return this->inputField.inputField.size();
}
void TTYConsole::Init()
{
this->HistorySetFile();
this->HistoryLoad();
}
void TTYConsole::Deinit()
{
this->End();
this->HistoryAppendChanges();
}
bool TTYConsole::Start()
{
if (!ConsoleStd::IsStdOutTTY())
{
return false;
}
gTTYConsoleEnabled = true;
#if defined(AURORA_IS_MODERNNT_DERIVED)
this->oldCP = GetConsoleOutputCP();
SetConsoleOutputCP(CP_UTF8);
#endif
#if defined(AURORA_IS_POSIX_DERIVED)
TTYWrite("\033[?1049h");
#endif
if (NoncanonicalMode())
{
ConsoleStd::EnterNoncanonicalMode();
}
#if defined(AURORA_IS_MODERNNT_DERIVED)
if (IsWin32UxMode())
{
UXModeStart();
}
#endif
return true;
}
void TTYConsole::End()
{
#if defined(AURORA_IS_MODERNNT_DERIVED)
SetConsoleActiveScreenBuffer(GetStdHandle(STD_OUTPUT_HANDLE));
#endif
if (!AuExchange(gTTYConsoleEnabled, false))
{
#if defined(AURORA_IS_POSIX_DERIVED)
TTYWrite("\033[?1049l");
#endif
return;
}
if (this->NoncanonicalMode())
{
ConsoleStd::LeaveNoncanonicalMode();
}
if (this->PermitDoubleBuffering())
{
EndBuffering();
}
TTYClearScreen();
TTYClearLine(EAnsiColor::eReset);
#if defined(AURORA_IS_POSIX_DERIVED)
TTYWrite("\033[?1049l");
#endif
#if defined(AURORA_IS_MODERNNT_DERIVED)
SetConsoleOutputCP(this->oldCP);
#endif
if (!UTF8())
{
TTYWrite("\033(B");
}
#if defined(AURORA_IS_POSIX_DERIVED)
ConsoleStd::Unlock(); // just really making sure
#endif
ConsoleStd::Flush();
}
void TTYConsole::BufferMessage(const AuConsole::ConsoleMessage &msg)
{
AU_LOCK_GUARD(this->messageLock);
AuConsole::ConsoleMessage msg2(msg);
msg2.line = msg.ToConsole();
AuTryInsert(this->messagesPending, msg2); // TODO: !
}
void TTYConsole::BufferMessage(const AuConsole::ConsoleMessage &msg, const AuString &input)
{
AU_LOCK_GUARD(this->messageLock);
AuConsole::ConsoleMessage msg2(msg);
msg2.line = input;
AuTryInsert(this->messagesPending, msg2); // TODO: !
}
void TTYConsole::NoncanonicalTick()
{
ConsoleStd::NoncanonicalTick();
auto inputs = ConsoleStd::DequeueNoncanonicalInput();
if (inputs.size() && PermitDoubleBuffering())
{
BeginBuffering();
}
for (auto &input : inputs)
{
//AuLogDbg("[HANDLE] Key Stroke: {}, {}, {}. control: {}, alt: {}, shift: {}", ConsoleStd::ENoncanonicalInputToString(input.type), input.scrollDeltaY, input.string, input.isControlSequence, input.isAltSequence, input.isShiftSequence);
switch (input.type)
{
case ConsoleStd::ENoncanonicalInput::eInput:
{
NoncanonicalOnString(input.string);
break;
}
case ConsoleStd::ENoncanonicalInput::eBackspace:
{
NoncanonicalOnBackspace();
break;
}
case ConsoleStd::ENoncanonicalInput::eEnter:
{
NoncanonicalOnEnter();
break;
}
case ConsoleStd::ENoncanonicalInput::eArrowLeft:
{
NoncanonicalOnLeft(input.bIsShiftSequence || input.bIsAltSequence, input.bIsControlSequence);
break;
}
case ConsoleStd::ENoncanonicalInput::eArrowRight:
{
NoncanonicalOnRight(input.bIsShiftSequence || input.bIsAltSequence, input.bIsControlSequence);
break;
}
case ConsoleStd::ENoncanonicalInput::ePageDown:
{
NoncanonicalOnPageDown();
break;
}
case ConsoleStd::ENoncanonicalInput::ePageUp:
{
NoncanonicalOnPageUp();
break;
}
case ConsoleStd::ENoncanonicalInput::eArrowDown:
{
if (this->inputField.noncanonicalCursorPosInBytes == 0 ||
this->GetHintLines() == 0 ||
this->inputField.noncanonicalCursorPosInBytes == this->inputField.inputField.size())
{
NoncanonicalOnHistoryDown();
}
else
{
NoncanonicalOnMenuDown();
}
break;
}
case ConsoleStd::ENoncanonicalInput::eArrowUp:
{
if (this->inputField.noncanonicalCursorPosInBytes == 0 ||
this->GetHintLines() == 0 ||
this->inputField.noncanonicalCursorPosInBytes == this->inputField.inputField.size())
{
NoncanonicalOnHistoryUp();
}
else
{
NoncanonicalOnMenuUp();
}
break;
}
case ConsoleStd::ENoncanonicalInput::eScroll:
{
if (abs(input.iScrollDeltaY) == 1)
{
input.iScrollDeltaY = input.iScrollDeltaY * 3;
}
Scroll(input.iScrollDeltaY);
break;
}
default:
{
AuLogDbg("Key Stroke: {}, {}, {}", ConsoleStd::ENoncanonicalInputToString(input.type), input.iScrollDeltaY, input.string);
}
};
}
if (inputs.size() && PermitDoubleBuffering())
{
EndBuffering();
}
}
void TTYConsole::NoncanonicalOnString(const AuString &input)
{
inputField.AddString(input);
RedrawInput(true);
NoncanonicalSetCursor();
}
void TTYConsole::NoncanonicalOnLeft(bool bShift, bool bControl)
{
inputField.DirectionalArrow(-1, bShift, bControl);
NoncanonicalSetCursor();
}
void TTYConsole::NoncanonicalOnRight(bool bShift, bool bControl)
{
inputField.DirectionalArrow(1, bShift, bControl);
NoncanonicalSetCursor();
}
void TTYConsole::NoncanonicalOnBackspace()
{
if (!inputField.Backspace())
{
return;
}
RedrawInput(true);
NoncanonicalSetCursor();
}
bool TTYConsole::IsWin32UxMode()
{
return true;
}
void TTYConsole::UXModeStart()
{
#if defined(AURORA_IS_MODERNNT_DERIVED)
LeaveScrollMode();
#endif
}
void TTYConsole::UXModeFlip()
{
if (AuExchange(uxModeFlipped, true))
{
return;
}
if (!IsWin32UxMode())
{
return;
}
#if defined(AURORA_IS_MODERNNT_DERIVED)
EnterScrollMode();
#endif
}
void EnterScrollMode()
{
#if defined(AURORA_IS_MODERNNT_DERIVED)
DWORD mode;
HANDLE stream = GetStdHandle(STD_INPUT_HANDLE);
HANDLE handles2[2];
GetConsoleHandles(handles2);
{
GetConsoleMode(stream, &mode);
mode &= ~(ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS);
SetConsoleMode(stream, mode);
}
{
if (handles2[0] != INVALID_HANDLE_VALUE)
{
GetConsoleMode(handles2[0], &mode);
mode &= ~(ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS);
SetConsoleMode(handles2[0], mode);
}
}
{
if (handles2[1] != INVALID_HANDLE_VALUE)
{
GetConsoleMode(handles2[1], &mode);
mode &= ~(ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS);
SetConsoleMode(handles2[1], mode);
}
}
gTTYConsole.uxModeFlipped = true;
#endif
}
void LeaveScrollMode()
{
#if defined(AURORA_IS_MODERNNT_DERIVED)
DWORD mode;
HANDLE stream = GetStdHandle(STD_INPUT_HANDLE);
HANDLE handles2[2];
GetConsoleHandles(handles2);
{
GetConsoleMode(stream, &mode);
mode |= ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS;
SetConsoleMode(stream, mode);
}
{
if (handles2[0] != INVALID_HANDLE_VALUE)
{
GetConsoleMode(handles2[0], &mode);
mode |= ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS;
SetConsoleMode(handles2[0], mode);
}
}
{
if (handles2[1] != INVALID_HANDLE_VALUE)
{
GetConsoleMode(handles2[1], &mode);
mode |= ENABLE_QUICK_EDIT_MODE | ENABLE_EXTENDED_FLAGS;
SetConsoleMode(handles2[1], mode);
}
}
gTTYConsole.uxModeFlipped = false;
gTTYConsole.uxFlipTime = AuTime::SteadyClockNS(); // Auto timeout
// TODO: we should use our other exit paths to capture panic and unsafe shutdown events
{
static AuInitOnce gInitOnce;
gInitOnce.Call([]()
{
atexit([]()
{
EnterScrollMode();
Exit();
});
});
}
#endif
}
void ScrollModeTick()
{
#if defined(AURORA_IS_MODERNNT_DERIVED)
if (!gTTYConsole.uxModeFlipped)
{
if (gTTYConsole.uxFlipTime + AuMSToNS<AuUInt64>(AuSToMS<AuUInt64>(8)) <= AuTime::SteadyClockNS())
{
EnterScrollMode();
}
}
#endif
}
void TTYConsole::NoncanonicalOnEnter()
{
AU_LOCK_GUARD(this->historyLock->AsWritable());
if (this->inputField.inputField.size())
{
#if defined(AURORA_IS_MODERNNT_DERIVED)
if (this->inputField.inputField == "!s")
{
EnterScrollMode();
}
else if (this->inputField.inputField == "!c")
{
LeaveScrollMode();
}
else
#endif
if (this->inputField.inputField == "!b")
{
this->iScrollPos = -1;
this->bTriggerRedraw = true;
}
else if (this->inputField.inputField == "!t")
{
this->iScrollPos = 0;
this->bTriggerRedraw = true;
}
else
{
#if defined(AURORA_IS_MODERNNT_DERIVED)
if (this->inputField.inputField == "help")
{
AuLogInfo("ConsoleTTY: Type !s to enter scroll mode, type !c to enter host-os controlled copy/paste mode (quick-edit)");
}
#endif
#if defined(AURORA_IS_POSIX_DERIVED)
if (this->inputField.inputField == "help")
{
AuLogInfo("ConsoleTTY: Hold control + arrow key up/down to scroll.");
}
#endif
if (this->inputField.inputField == "help")
{
AuLogInfo("ConsoleTTY: use the command !t to scroll to the top, the command !b to lock-scroll to the bottom.");
}
AuConsole::DispatchRawLine(this->inputField.inputField);
}
AuTryInsert(this->history, this->inputField.inputField);
}
this->inputField.Clear();
OnEnter();
}
void TTYConsole::NoncanonicalOnMenuLeft()
{
}
void TTYConsole::NoncanonicalOnMenuRight()
{
}
void TTYConsole::NoncanonicalOnMenuUp()
{
}
void TTYConsole::NoncanonicalOnMenuDown()
{
}
void TTYConsole::NoncanonicalOnHistoryUp()
{
bool locked {};
if (this->history.empty())
{
return;
}
if (this->inputField.noncanonicalCursorPos == 0 ||
this->inputField.noncanonicalCursorPosInBytes == this->inputField.inputField.size())
{
locked = true;
}
if (this->iHistoryPos == -1)
{
this->iHistoryPos = this->history.size();
}
this->iHistoryPos--;
if (this->iHistoryPos < 0)
{
this->iHistoryPos = 0;
}
this->inputField.inputField = this->history[this->iHistoryPos];
HistoryUpdateCursor();
if (locked)
{
this->inputField.noncanonicalCursorPos = GuessWidth(this->inputField.inputField);
this->inputField.noncanonicalCursorPosInBytes = (int)this->inputField.inputField.size();
NoncanonicalSetCursor();
}
this->inputField.DoUnselectTick();
}
void TTYConsole::NoncanonicalOnHistoryDown()
{
AU_LOCK_GUARD(this->historyLock->AsReadable());
bool locked {};
if (this->history.empty())
{
return;
}
if (this->inputField.noncanonicalCursorPos == 0 ||
this->inputField.noncanonicalCursorPosInBytes == this->inputField.inputField.size())
{
locked = true;
}
if (this->iHistoryPos == -1)
{
return;
}
this->iHistoryPos++;
if (this->iHistoryPos >= this->history.size())
{
this->iHistoryPos = -1;
this->inputField.inputField.clear();
}
else
{
this->inputField.inputField = this->history[this->iHistoryPos];
}
HistoryUpdateCursor();
if (locked)
{
this->inputField.noncanonicalCursorPos = GuessWidth(this->inputField.inputField);
this->inputField.noncanonicalCursorPosInBytes = (int)this->inputField.inputField.size();
NoncanonicalSetCursor();
}
this->inputField.DoUnselectTick();
}
void TTYConsole::HistoryUpdateCursor()
{
this->inputField.noncanonicalCursorPos = AuMin<AuUInt32>(this->inputField.noncanonicalCursorPos, GuessWidth(this->inputField.inputField));
this->inputField.noncanonicalCursorPosInBytes = AuMin<AuUInt32>(this->inputField.noncanonicalCursorPosInBytes, AuUInt32(this->inputField.inputField.size()));
RedrawInput(true);
NoncanonicalSetCursor();
}
void TTYConsole::HistorySetFile()
{
auto pProcPath = Process::GetProcessFullPath();
auto hash = AuFnv1a64Runtime(pProcPath ? pProcPath->data() : nullptr,
pProcPath ? pProcPath->size() : 0);
AuIOFS::NormalizePath(this->historyFileName, fmt::format("~/TTYHistory/{}.txt", hash));
}
AuString TTYConsole::HistoryGetFile()
{
return this->historyFileName;
}
void TTYConsole::HistoryAppendChanges()
{
if (!this->historyLock)
{
return;
}
AU_LOCK_GUARD(this->historyLock->AsWritable());
if (this->history.size() <= this->iHistoryWritePos)
{
return;
}
auto file = AuIOFS::OpenUnique(this->historyFileName, AuIOFS::EFileOpenMode::eReadWrite);
if (!file)
{
SysPushErrorIO(this->historyFileName);
return;
}
file->SetOffset(file->GetLength());
AuString buffer;
auto line = AuLocale::NewLine();
line.reserve(4096);
for (int i = this->iHistoryWritePos; i < this->history.size(); i++)
{
buffer += (i == 0 ? "" : line);
buffer += this->history[i];
}
AuUInt bytesWritten;
file->Write(AuMemoryViewStreamRead(AuMemory::MemoryViewRead(buffer), bytesWritten));
this->iHistoryWritePos = this->history.size();
}
void TTYConsole::HistoryLoad()
{
AU_LOCK_GUARD(this->historyLock->AsWritable());
if (this->historyFileName.empty())
{
return;
}
if (!AuIOFS::FileExists(this->historyFileName))
{
return;
}
AuString buffer;
if (!AuIOFS::ReadString(this->historyFileName, buffer))
{
return;
}
AuParse::SplitNewlines(buffer, [&](const AuString &in)
{
this->history.push_back(in);
});
this->iHistoryWritePos = this->history.size();
}
void TTYConsole::NoncanonicalOnPageUp()
{
this->Scroll(this->GetLogBoxLines());
}
void TTYConsole::NoncanonicalOnPageDown()
{
this->Scroll(-this->GetLogBoxLines());
}
void TTYConsole::Scroll(int delta)
{
auto maxLines = GetLogBoxLines();
if (this->screenBuffer.size() <= maxLines)
{
this->iScrollPos = -1;
return;
}
if (this->iScrollPos == -1)
{
this->iScrollPos = AuMax<AuUInt32>(this->screenBuffer.size() - maxLines, 0);
}
this->iScrollPos -= delta;
if (this->iScrollPos < 0)
{
this->iScrollPos = 0;
}
if (this->screenBuffer.size() - maxLines < this->iScrollPos )
{
this->iScrollPos = -1;
}
this->bTriggerRedraw = true;
}
void TTYConsole::PumpHistory()
{
static Aurora::Utility::RateLimiter limiter;
if (!limiter.nextTriggerTime)
{
limiter.noCatchUp = true;
limiter.SetNextStep(AuMSToNS<AuUInt64>(10'000));
}
if (!limiter.CheckExchangePass())
{
return;
}
HistoryAppendChanges();
}
void TTYConsole::Pump()
{
//SysBenchmark("Console TTY pump");
if (!gTTYConsoleEnabled)
{
return;
}
if (NoncanonicalMode())
{
//SysBenchmark("NoncanonicalTick");
NoncanonicalTick();
}
{
//SysBenchmark("flush (redraw)");
Flush();
}
{
//SysBenchmark("history");
PumpHistory();
}
ScrollModeTick();
}
void TTYConsole::OnEnter()
{
this->iHistoryPos = -1;
auto line = this->currentHeight;
this->inputField.Clear();
this->RedrawInput(true);
this->NoncanonicalSetCursor();
}
void TTYConsole::Flush()
{
AU_LOCK_GUARD(this->messageLock);
RedrawWindow();
}
bool TTYConsole::RegenerateBuffer(bool resChanged, bool &forceRedrawIfFalse)
{
auto messagesPending = AuExchange(this->messagesPending, {});
bool bShouldRegenerateStringArary {};
bool widthChanged = this->oldWidth != this->currentWidth;
bool sizeChanged = widthChanged || this->currentHeight != this->oldHeight;
// ew
auto maxLines = GetLogBoxLines();
int maxWidth = this->currentWidth - int(GetRightBorder()) - int(GetLeftBorder()) - int(this->leftLogPadding) - this->rightLogPadding;
// Word-wrap cancer
if (this->bScreenBufferDoesntMap)
{
if (resChanged)
{
bShouldRegenerateStringArary = widthChanged;
}
}
else
{
if (resChanged)
{
for (auto &[color, message] : this->screenBuffer)
{
int anyLineRemoveEsc = message.rfind('\x1b', 0) == 0;
if (message.size() - (anyLineRemoveEsc * 7) > maxWidth)
{
bShouldRegenerateStringArary = true;
break;
}
}
}
}
// Unsafe insert
this->messages.insert(this->messages.end(), messagesPending.begin(), messagesPending.end());
if (this->messages.size() > 10000)
{
this->messages.erase(this->messages.begin(), this->messages.end() - 10000);
}
// Insert console message while attempting to preserve the intention of word-wrapping
auto addMessages = [&](const AuList<AuConsole::ConsoleMessage> &messages)
{
for (auto &message : messages)
{
auto str = message.line; // Note: we replaced the line in BufferMessage
while (str.size())
{
int XOffset = GetLeftBorder() + this->leftLogPadding;
auto itr = str.npos;
itr = str.find('\t');
while (itr != str.npos)
{
auto suffix = str.substr(itr + 1);
str = str.substr(0, itr);
AuString padding(4 - (itr % 4), ' ');
str += padding;
str += suffix;
itr = str.find('\t', itr);
}
int anyLineRemoveEsc = str.rfind('\x1b', 0) == 0;
int idx = AuLocale::Encoding::CountUTF8Length({str.data(), AuMin<AuUInt>(str.size(), maxWidth + (anyLineRemoveEsc * 7))}, true);
auto append = str.substr(0, idx);
AuTryInsert(this->screenBuffer, AuMakePair(message.color, append));
str = str.substr(idx);
this->bScreenBufferDoesntMap |= bool(str.size());
}
}
if (this->screenBuffer.size() > maxLines)
{
UXModeFlip();
}
};
if (bShouldRegenerateStringArary)
{
// Recalculate our AuString array of lines
this->screenBuffer.clear();
addMessages(this->messages);
// Redraw entire screenbuffer
return true;
}
else
{
// Fast path - just append the damn changes
int oldSize = this->screenBuffer.size();
addMessages(messagesPending);
auto bannerLines = this->GetBannerLines();
auto indexBeforePadding = this->GetTopBorder() + this->GetBannerFootBorder() + (bannerLines ? this->topLogPadding + bannerLines : 0) ;
auto startIndex = this->topLogPaddingExtra + indexBeforePadding;
int drawPos = {};
auto delta = this->screenBuffer.size() - oldSize;
if (iScrollPos == -1)
{
if (this->screenBuffer.size() > maxLines)
{
// TODO (Reece): Removing optimization bc it was broken. This is a nightmare.
if (!gBuggyFastAppend)
{
static bool bSingleShot {};
if (!AuExchange(bSingleShot, true))
{
EnterScrollMode();
}
auto indexBeforePadding = (this->GetBannerFootBorder() ? 1 + this->topLogPadding : 0) + this->topLogPaddingExtra;
auto startIndex = this->GetTopBorder() + this->GetBannerLines() + indexBeforePadding;
for (int i = startIndex; i < this->currentHeight; i++)
{
this->BlankLine(i);
}
this->screenBuffer.clear();
addMessages(this->messages);
return true;
}
else
{
// this is still buggy as shit but we NEEEEED it
// youll notice whitespace appear as soon as we start overflowing but eh
for (int i = startIndex + maxLines; i < this->currentHeight; i++)
{
this->BlankLine(i);
}
TTYScrollBuffer(delta);
for (int i = indexBeforePadding; i < startIndex; i++)
{
this->BlankLine(i);
}
forceRedrawIfFalse = true;
drawPos = startIndex + maxLines - delta;
}
}
else
{
drawPos = startIndex + oldSize;
}
}
else
{
return false;
}
if (delta >= this->GetLogBoxLines())
{
return true;
}
else
{
for (int i = 0; i < delta; i++)
{
auto &[color, message] = this->screenBuffer[i + oldSize];
WriteBufferedEx({GetLeftBorder() + this->leftLogPadding, i + drawPos}, color, message);
}
}
return false || resChanged;
}
}
bool TTYConsole::UTF8()
{
#if defined(AURORA_IS_MODERNNT_DERIVED)
return true;
#else
return AuLocale::GetLocale().codepage == AuLocale::ECodePage::eUTF8;
#endif
}
bool TTYConsole::RedrawWindow()
{
bool bTryAgain {};
bool bRedrawn = {};
bool bRefresh {};
do
{
bTryAgain = false;
bool bWarmed {};
#if defined(AURORA_IS_MODERNNT_DERIVED)
bWarmed = WarmBuffering();
#endif
auto pos = TTYScreenSize();
bool bChangedWidth = currentWidth != pos.first;
bool bChangedHeight = currentHeight != pos.second;
bool bChangedSize = bChangedWidth || bChangedHeight || bWarmed;
bool bRedrawWindowRequired = this->bTriggerRedraw;
bool bHasMesasgesPending = messagesPending.size() || this->bTriggerRedraw;
auto currentHash = AuFnv1a64Runtime(inputField.inputField.data(), inputField.inputField.size());
bool bInputChanged = NoncanonicalMode() && (lastInputHash != currentHash && lastInputHash != 0);
lastInputHash = currentHash;
oldWidth = currentWidth;
oldHeight = currentHeight;
currentWidth = pos.first;
currentHeight = pos.second;
if (currentWidth < 0)
{
bTryAgain = true;
continue;
}
bool redrawEntireBox {};
if (bChangedSize || bRedrawWindowRequired || bHasMesasgesPending)
{
bRedrawn = true;
if (PermitDoubleBuffering())
{
BeginBuffering();
}
if (NoncanonicalMode())
{
}
else
{
TTYStorePos();
}
if (bChangedSize || this->bTriggerRedraw)
{
TTYClearScreen();
bRefresh = true;
redrawEntireBox = true;
}
}
if (bChangedSize || bHasMesasgesPending)
{
bool redrawBox {};
bRefresh = bChangedSize || this->bTriggerRedraw;
if (RegenerateBuffer(bChangedSize, redrawBox) || this->bTriggerRedraw)
{
bRefresh = true;
redrawEntireBox = true;
RedrawLogBox();
}
bRefresh |= redrawBox;
}
if (redrawEntireBox)
{
RedrawLogBox();
}
if (bRefresh)
{
RedrawBanner();
RedrawBorders();
}
if (bRedrawn || bRefresh)
{
RedrawInput(NoncanonicalMode());
this->NoncanonicalSetCursor();
TTYStorePos();
}
if (bRedrawn || bRefresh)
{
RedrawHintLine();
}
#if 0
DebugLogArea();
#endif
if (bRedrawn || bRefresh)
{
if (NoncanonicalMode())
{
NoncanonicalSetCursor();
}
else
{
TTYRestorePos();
}
{
// Account for race conditions of size-change during draw
if (PermitDoubleBuffering())
{
bTryAgain = !EndBuffering();
}
}
}
this->bTriggerRedraw = false;
}
while (bTryAgain && false);
return bRedrawn;
}
void TTYConsole::DebugLogArea()
{
auto lines = this->GetLogBoxLines();
auto start = this->GetTopBorder() +
(this->GetBannerLines() ? this->GetBannerLines() + 1 : 0) +
(this->GetBannerFootBorder() ? this->topLogPadding : 0) +
this->topLogPaddingExtra;
AuString blank(this->currentWidth - GetRightBorder() - GetLeftBorder() - this->leftLogPadding - this->rightLogPadding, '~');
for (int i = 0; i < lines; i++)
{
WriteBuffered({GetLeftBorder() + this->leftLogPadding, i + start}, blank);
}
}
void TTYConsole::WriteLine(int Y, const AuString &in)
{
#if defined(AURORA_IS_MODERNNT_DERIVED)
RecordFunction(std::bind(&TTYConsole::WriteLine, this, Y, std::string(in)));
#endif
if (in.empty())
{
return;
}
int XOffset {};
XOffset = GetLeftBorder();
#if defined(AURORA_IS_MODERNNT_DERIVED)
DWORD idc;
auto line2 = AuLocale::ConvertFromUTF8(in);
SetConsoleCursorPosition(GetTTYHandle(), COORD { AuStaticCast<short>(XOffset), AuStaticCast<short>(Y) });
WriteConsoleW(GetTTYHandle(), line2.data(), AuUInt32(line2.size()), &idc, NULL);
#else
TTYSetPos({XOffset, Y});
ConsoleStd::WriteStdOutBlocking2(in.data(), in.size());
#endif
}
void TTYConsole::BlankBordersLine(int Y)
{
AuString bar;
if (UTF8())
{
bar = "\xe2\x94\x82";
}
else
{
bar = "\033(0x\033(B";
}
WriteBuffered({0, Y}, bar);
WriteBuffered({this->currentWidth - 1, Y}, bar);
}
void TTYConsole::BlankLine(int Y, bool borders)
{
TTYSetPos({0, Y});
TTYClearLine(EAnsiColor::eReset);
BlankBordersLine(Y);
}
void TTYConsole::WriteBuffered(AuPair<AuUInt32, AuUInt32> pos, const AuString &in)
{
#if defined(AURORA_IS_MODERNNT_DERIVED)
RecordFunction(std::bind(&TTYConsole::WriteBuffered, this, pos, std::string(in)));
#endif
if (in.empty())
{
return;
}
#if defined(AURORA_IS_MODERNNT_DERIVED)
DWORD idc;
auto line2 = AuLocale::ConvertFromUTF8(in);
SetConsoleCursorPosition(GetTTYHandle(), COORD { AuStaticCast<short>(pos.first), AuStaticCast<short>(pos.second) });
WriteConsoleW(GetTTYHandle(), line2.data(), AuUInt32(line2.size()), &idc, NULL);
#else
TTYSetPos(pos);
ConsoleStd::WriteStdOutBlocking2(in.data(), in.size());
#endif
}
void TTYConsole::WriteBufferedEx(AuPair<AuUInt32, AuUInt32> pos, EAnsiColor color, const AuString &in)
{
#if defined(AURORA_IS_MODERNNT_DERIVED)
RecordFunction(std::bind(&TTYConsole::WriteBufferedEx, this, pos, color, std::string(in)));
#endif
if (in.empty())
{
return;
}
#if defined(AURORA_IS_MODERNNT_DERIVED)
DWORD idc;
{
auto hConsole = GetTTYHandle();
DWORD attrib {};
if (color != EAnsiColor::eEnumCount)
{
attrib |= kAnsiColorForegroundToNT[AuStaticCast<AuUInt>(color)];
}
else
{
attrib = FOREGROUND_WHITE;
}
auto line2 = AuLocale::ConvertFromUTF8(in);
SetConsoleTextAttribute(hConsole, attrib);
SetConsoleCursorPosition(GetTTYHandle(), COORD { AuStaticCast<short>(pos.first), AuStaticCast<short>(pos.second) });
WriteConsoleW(hConsole, line2.data(), AuUInt32(line2.size()), &idc, NULL);
SetConsoleTextAttribute(hConsole, FOREGROUND_WHITE);
}
//WriteConsoleA(GetTTYHandle(), in.data(), AuUInt32(in.size()), &idc, NULL);
#else
TTYSetPos(pos);
AuString writeLine;
const char *pAnsiCode =
static_cast<EAnsiColor>(color) <= EAnsiColor::eEnumCount ?
kAnsiColorForegroundToVirtualEscape[static_cast<AuUInt>(color)] :
"";
writeLine = pAnsiCode + in + kAnsiColorForegroundToVirtualEscape[static_cast<AuUInt>(EAnsiColor::eReset)];
ConsoleStd::WriteStdOutBlocking2(writeLine.data(), writeLine.size());
#endif
}
void TTYConsole::RedrawBorders()
{
TTYSetPos({});
AuString start;
AuString end;
AuString bar;
AuString startLine;
AuString midLine;
AuString endLine;
startLine.clear();
if (!UTF8())
{
start = "\033(0";
end = "\033(B";
for (int i = 0; i < currentWidth; i++)
{
startLine += "q";
}
midLine = endLine = startLine;
if (GetLeftBorder())
{
startLine[0] = 'l';
}
if (GetRightBorder())
{
startLine[startLine.size() - 1] = 'k';
}
if (GetLeftBorder())
{
midLine[0] = 't';
}
if (GetRightBorder())
{
midLine[midLine.size() - 1] = 'u';
}
if (GetLeftBorder())
{
endLine[0] = 'm';
}
if (GetRightBorder())
{
endLine[endLine.size() - 1] = 'j';
}
bar = 'x';
}
else
{
AuString slash = "\xe2\x94\x80";
for (int i = GetLeftBorder(); i < currentWidth - int(GetRightBorder()); i++)
{
startLine += slash;
}
midLine = endLine = startLine;
if (GetLeftBorder())
{
startLine = "\xe2\x94\x8c" + startLine;
}
if (GetRightBorder())
{
startLine += "\xe2\x94\x90";
}
if (GetLeftBorder())
{
midLine = "\xe2\x94\x9c" + midLine;
}
if (GetRightBorder())
{
midLine += "\xe2\x94\xa4";
}
if (GetLeftBorder())
{
endLine = "\xe2\x94\x94" + endLine;
}
if (GetRightBorder())
{
endLine += "\xe2\x94\x98";
}
bar = "\xe2\x94\x82";
}
int height = currentHeight;
int widthM = currentWidth - 1;
// Begin drawing
WriteBuffered({}, start);
// Box: top
if (GetTopBorder())
{
WriteBuffered({}, Stringify(startLine, this->headerTitle, true, true, true));
}
// Box: sides
for (int i = GetTopBorder(); i < height - GetBottomBorder(); i++)
{
WriteBuffered({0, i}, bar);
WriteBuffered({widthM, i}, bar);
}
// Box: Bottom
if (GetBottomBorder())
{
WriteBuffered({0, height - 1}, endLine);
}
// Above hint splitter line
if (GetLogBoxHintBorder())
{
int l = this->GetHintLines();
// GetLogBoxHintBorder
WriteBuffered({0, this->currentHeight - (this->GetBottomBorder() + this->GetTextInputLines() + this->topInputPadding + this->GetLogBoxHintBorder() + (l ? (this->topHintPadding) : 0))}, midLine);
}
// Below subhead splitter line
if (this->GetBannerFootBorder())
{
int l = this->GetBannerLines();
if (l)
{
WriteBuffered({0, this->GetTopBorder() + this->GetBannerLines()}, Stringify(midLine, this->logTitle, true, true, true));
}
}
// End drawing mode
WriteBuffered({}, end);
}
const AuString &TTYConsole::Stringify(const AuString &referenceLine, const AlignedString &string, bool spacing, bool brackets, bool extraPadding)
{
if (string.str.empty())
{
return referenceLine;
}
this->tempMemory = referenceLine;
int lineWidth = this->currentWidth;
int stride = 1;
if (!referenceLine.empty())
{
if (UTF8())
{
stride = 3;
}
}
// HACK: if non-title, remove borders
// Before i'd drop the first & last char if line == width, but ofc this doesnt work with UTF8, and it'd pretty dumb to rely on an estimated char length
int L {}, R {};
if (!extraPadding)
{
lineWidth -= GetLeftBorder() * stride;
lineWidth -= GetRightBorder() * stride;
}
else
{
// Border offsets, if accounting for them.
L = GetLeftBorder();
R = GetRightBorder();
}
if (lineWidth < 0)
{
this->tempMemory.clear();
return this->tempMemory;
}
if (referenceLine.empty())
{
this->tempMemory = AuString(lineWidth, ' ');
}
int length = GuessWidth(string.str) + (brackets ? 2 : 0) + (spacing ? 2 : 0);
if (length > lineWidth)
{
return referenceLine;
}
int start {};
int padding2 = extraPadding ? 2 : 0;
// You're an idiot. you dont need a comment to work out what this does
switch (string.align)
{
case ETTYAlign::eLeft:
start = padding2 + L;
break;
case ETTYAlign::eRight:
start = lineWidth - (R + padding2 + length);
break;
case ETTYAlign::eCenter:
start = lineWidth / 2 - (length / 2);
break;
}
if (start + length + GetRightBorder() > this->currentWidth)
{
return referenceLine;
}
// In UTF8 mode, we fill the buffer of characters of a constant stride of 3
start *= stride;
if (start >= tempMemory.size())
{
return referenceLine;
}
// Brackets
AuString left;
AuString right;
if (brackets)
{
if (UTF8())
{
right = "\xe2\x94\x9c";
left = "\xe2\x94\xa4";
}
else
{
left = "u";
right = "t";
}
}
// Overhead to enter DEC drawing mode around the brackets on non-utf8 terminals
int DECOverhead = 0;
int DECOverheadA = 0;
int DECOverheadB = 0;
if (!UTF8())
{
DECOverheadA = DECOverheadB = 3;
DECOverhead = DECOverheadA + DECOverheadB;
}
// kanker
AuUInt toWrite = (brackets * stride) + DECOverheadA + spacing + string.str.size() + spacing + DECOverheadB + (brackets * stride);
AuUInt toSlice = stride * ((spacing * 2) + (brackets * 2) + string.str.size());
// brrr
if (toWrite <= toSlice)
{
// Fast path, pog. We dont need to allocate.
AuUInt strideTwo = brackets * stride;
if (toWrite < toSlice)
{
AuUInt second = start + strideTwo + DECOverheadA + spacing + string.str.size() + spacing + DECOverheadB + right.size();
AuUInt two = start + (length * strideTwo);
AuUInt count = this->tempMemory.size() - two;
AuMemmove(this->tempMemory.data() + second,
this->tempMemory.data() + two, this->tempMemory.size() - two);
this->tempMemory.resize(second + count);
}
if (brackets)
{
AuMemcpy(this->tempMemory.data() + start, left.data(), left.size());
}
if (spacing)
{
this->tempMemory.data()[start + strideTwo + DECOverheadA] = ' ';
}
if (DECOverheadA)
{
AuMemcpy(this->tempMemory.data() + strideTwo + DECOverheadA + spacing, "\033(B", 3);
}
AuMemcpy(this->tempMemory.data() + start + strideTwo + DECOverheadA + spacing, string.str.data(), string.str.size());
if (spacing)
{
this->tempMemory.data()[start + strideTwo + DECOverheadA + spacing + string.str.size() + DECOverheadB] = ' ';
}
if (DECOverheadB)
{
AuMemcpy(this->tempMemory.data() + start + strideTwo + DECOverheadA + spacing + string.str.size() + spacing, "\033(0", 3);
}
if (brackets)
{
AuMemcpy(this->tempMemory.data() + start + strideTwo + DECOverheadA + spacing + string.str.size() + spacing + DECOverheadB, right.data(), right.size());
}
}
else
{
// Slow allocation path
auto cpy = this->tempMemory; // alloc
AuString &ret = this->tempMemory;
auto begin = cpy.empty() ? "" : cpy.substr(0, start); // alloc
auto end = cpy.empty() ? "" : cpy.substr(start + (length * stride)); // alloc
ret = AuMove(begin);
ret.reserve(this->currentWidth * stride);
// potential allocs:
if (brackets)
{
ret += left;
}
if (DECOverheadA)
{
ret += "\033(B";
}
if (spacing)
{
ret += ' ';
}
ret += string.str;
if (spacing)
{
ret += ' ';
}
if (DECOverheadB)
{
ret += "\033(0";
}
if (brackets)
{
ret += right;
}
ret += AuMove(end);
return ret;
}
return this->tempMemory;
}
void TTYConsole::RedrawBanner()
{
if (this->header.str.size())
{
WriteLine(this->GetTopBorder() +
this->topHeaderPadding,
Stringify("", this->header, false, false, false));
}
if (this->subheader.str.size())
{
WriteLine(this->GetTopBorder() +
(this->header.str.size() ? 1 + this->topHeaderPadding : 0) +
(this->midHeaderPadding),
Stringify("", this->subheader, false, false, false));
}
}
void TTYConsole::RedrawLogBox()
{
auto indexBeforePadding = (this->GetBannerFootBorder() ? 1 + this->topLogPadding : 0) + this->topLogPaddingExtra;
auto startIndex = this->GetTopBorder() + this->GetBannerLines() + indexBeforePadding;
//auto startIndex2 = this->topLogPaddingExtra + indexBeforePadding;
int drawPos = startIndex;
auto maxLines = GetLogBoxLines();
for (int i = startIndex + maxLines; i < this->currentHeight; i++)
{
this->BlankLine(i);
}
auto startingLineBufferIndex = 0;
if (iScrollPos == -1)
{
if (this->screenBuffer.size() > maxLines)
{
startingLineBufferIndex = this->screenBuffer.size() - maxLines;
}
else
{
startingLineBufferIndex = 0;
}
}
else
{
startingLineBufferIndex = iScrollPos;
}
for (int i = 0; i < maxLines; i++)
{
auto strIdx = i + startingLineBufferIndex;
if (strIdx >= this->screenBuffer.size())
{
break;
}
auto &[color, message] = this->screenBuffer[strIdx];
WriteBufferedEx({ GetLeftBorder() + this->leftLogPadding, i + drawPos }, color, message);
//WriteBuffered({GetLeftBorder() + this->leftLogPadding, i + startIndex}, this->screenBuffer[strIdx].second);
}
}
void TTYConsole::BlankLogBox()
{
}
void TTYConsole::RedrawInput(bool clear)
{
int height = this->currentHeight - (1 + this->GetBottomBorder());
if (clear)
{
BlankLine(height);
}
WriteBuffered({this->GetLeftBorder() + this->leftPadding, height}, ">");
if (clear && NoncanonicalMode())
{
this->inputField.WriteBuffered(this, { this->GetLeftBorder() + this->leftPadding + 1, height });
}
}
void TTYConsole::RedrawHintLine()
{
}
int TTYConsole::GetHintLines()
{
return this->hintStrings.size();
}
bool TTYConsole::GetLogBoxHintBorder()
{
return true;
}
bool TTYConsole::GetBottomBorder()
{
return true;
}
ETTYAlign TTYConsole::GetTitleAlignment()
{
return this->headerTitle.align;
}
ETTYAlign TTYConsole::SetTitleAlignment(ETTYAlign newValue)
{
return AuExchange(this->headerTitle.align, newValue);
}
ETTYAlign TTYConsole::GetLogBoxTitleAlignment()
{
return this->logTitle.align;
}
ETTYAlign TTYConsole::SetLogBoxTitleAlignment(ETTYAlign newValue)
{
return AuExchange(this->logTitle.align, newValue);
}
ETTYAlign TTYConsole::GetHeaderAlignment()
{
return this->header.align;
}
ETTYAlign TTYConsole::GetSubheaderAlignment()
{
return this->subheader.align;
}
AuString TTYConsole::GetHeaderBorderTitle()
{
return this->headerTitle.str;
}
AuString TTYConsole::GetLogBoxBorderTitle()
{
return this->logTitle.str;
}
AuString TTYConsole::GetHeader()
{
return this->header.str;
}
AuString TTYConsole::GetSubHeader()
{
return this->subheader.str;
}
ETTYAlign TTYConsole::SetHeaderAlignment(ETTYAlign newValue)
{
this->bTriggerRedraw = true;
return AuExchange(this->header.align, newValue);
}
ETTYAlign TTYConsole::SetSubheaderAlignment(ETTYAlign newValue)
{
this->bTriggerRedraw = true;
return AuExchange(this->subheader.align, newValue);
}
AuString TTYConsole::SetHeaderBorderTitle(const AuString &newValue)
{
this->bTriggerRedraw = true;
return AuExchange(this->headerTitle.str, newValue);
}
AuString TTYConsole::SetLogBoxBorderTitle(const AuString &newValue)
{
this->bTriggerRedraw = true;
return AuExchange(this->logTitle.str, newValue);
}
AuString TTYConsole::SetHeader(const AuString &newValue)
{
this->bTriggerRedraw = true;
return AuExchange(this->header.str, newValue);
}
AuString TTYConsole::SetSubHeader(const AuString &newValue)
{
this->bTriggerRedraw = true;
return AuExchange(this->subheader.str, newValue);
}
bool TTYConsole::SetTopBorder(bool newValue)
{
this->bTriggerRedraw = true;
return AuExchange(this->bTopBorder, newValue);
}
bool TTYConsole::SetLeftBorder(bool newValue)
{
this->bTriggerRedraw = true;
return AuExchange(this->bLeftBorder, newValue);
}
bool TTYConsole::SetRightBorder(bool newValue)
{
this->bTriggerRedraw = true;
return AuExchange(this->bRightBorder, newValue);
}
int TTYConsole::GetBannerLines()
{
int ret = !this->header.str.empty() + !this->subheader.str.empty();
if (!ret)
{
return 0;
}
return ret + this->midHeaderPadding + (this->header.str.size() ? this->topHeaderPadding : 0) + (this->subheader.str.size() ? this->bottomSubHeaderPadding : 0);
}
int TTYConsole::GetLogBoxLines()
{
int hintLines = this->GetHintLines();
int bannerLines = this->GetBannerLines();
//
return this->currentHeight - (this->GetTopBorder() +
//////////////////
bannerLines +
//////////////////
(bannerLines ? 1 /* splitter */ + this->topLogPadding : 0) +
this->topLogPaddingExtra +
GetLogBoxHintBorder() +
//////////////////////////////
1 +
(bool(hintLines) ? (hintLines + this->topHintPadding) : 0) +
this->topInputPadding +
//////////////////////////////
this->GetTextInputLines() +
//////////////////////////////
this->GetBottomBorder());
}
int TTYConsole::GetTextInputLines()
{
return 1;
}
bool TTYConsole::GetBannerFootBorder()
{
return this->header.str.size() || this->subheader.str.size();
}
bool TTYConsole::GetTopBorder()
{
return this->bTopBorder;
}
bool TTYConsole::GetRightBorder()
{
return this->bRightBorder;
}
bool TTYConsole::GetLeftBorder()
{
return this->bLeftBorder;
}
bool TTYConsole::NoncanonicalMode()
{
return ConsoleStd::IsStdOutTTY();
}
bool TTYConsole::PermitDoubleBuffering()
{
// We can't double buffer and not share the input line between two framebuffers
#if defined(AURORA_IS_MODERNNT_DERIVED)
return NoncanonicalMode();
#endif
return true;
}
AuPair<AuUInt32, AuUInt32> TTYConsole::GetLogBoxStart()
{
return {this->GetLeftBorder(), this->GetTopBorder() + this->GetBannerLines() + this->GetBannerFootBorder()};
}
AuPair<AuUInt32, AuUInt32> TTYConsole::GetHintLine()
{
int hintLines = this->GetHintLines();
return {this->GetLeftBorder(), this->currentHeight - (1 + hintLines + bool(hintLines) + this->GetBottomBorder() + this->GetTextInputLines())};
}
AuPair<AuUInt32, AuUInt32> TTYConsole::GetInputCursor()
{
if (!this->NoncanonicalMode())
{
return {this->GetLeftBorder() + this->leftPadding + 1 + this->leftInputPadding, this->currentHeight - (1 + this->GetBottomBorder())};
}
return {this->GetLeftBorder() + this->leftPadding + 1 + this->leftInputPadding + (this->inputField.noncanonicalCursorPos == -1 ? 0 : this->inputField.noncanonicalCursorPos) /*:(*/, this->currentHeight - (1 + this->GetBottomBorder())}; // { i do not like this, ...}
}
AuPair<AuUInt32, AuUInt32> TTYConsole::GetInitialInputLine()
{
return {};
}
void TTYConsole::NoncanonicalSetCursor()
{
TTYSetPos(this->GetInputCursor());
}
void TTYConsole::SetHintStrings(const AuList<AuPair<AuString /*autocompletion*/, AuList<AuString/* secondary hint*/>>> &hints)
{
this->hintStrings = AuMove(hints);
}
bool TTYConsole::CheckRedraw()
{
return {};
}
void TTYConsole::SetInitialCursorPos()
{
}
void TTYConsole::PostRedraw(bool first)
{
if (first)
{
SetInitialCursorPos();
//AuConsole::TTYSetPos()
}
else if (NoncanonicalMode())
{
}
}
AuUInt8 TTYConsole::SetPaddingLeftOfLog(AuUInt8 newValue)
{
this->bTriggerRedraw = true;
return AuExchange(this->leftLogPadding, newValue);
}
AuUInt8 TTYConsole::SetPaddingRightOfLog(AuUInt8 newValue)
{
this->bTriggerRedraw = true;
return AuExchange(this->rightLogPadding, newValue);
}
AuUInt8 TTYConsole::SetPaddingLeftOfInput(AuUInt8 newValue)
{
this->bTriggerRedraw = true;
return AuExchange(this->leftInputPadding, newValue);
}
AuUInt8 TTYConsole::SetPaddingTopOfInput(AuUInt8 newValue)
{
this->bTriggerRedraw = true;
return AuExchange(this->topInputPadding, newValue);
}
AuUInt8 TTYConsole::SetPaddingTopOfHint(AuUInt8 newValue)
{
this->bTriggerRedraw = true;
return AuExchange(this->topHintPadding, newValue);
}
AuUInt8 TTYConsole::SetPaddingTopOfHeader(AuUInt8 newValue)
{
this->bTriggerRedraw = true;
return AuExchange(this->topHeaderPadding, newValue);
}
AuUInt8 TTYConsole::SetPaddingMidOfHeader(AuUInt8 newValue)
{
this->bTriggerRedraw = true;
return AuExchange(this->midHeaderPadding, newValue);
}
AuUInt8 TTYConsole::SetPaddingBottomOfSubheader(AuUInt8 newValue)
{
this->bTriggerRedraw = true;
return AuExchange(this->bottomSubHeaderPadding, newValue);
}
AuUInt8 TTYConsole::GetPaddingLeftOfLog()
{
return this->leftLogPadding;
}
AuUInt8 TTYConsole::GetPaddingRightOfLog()
{
return this->rightLogPadding;
}
AuUInt8 TTYConsole::GetPaddingLeftOfInput()
{
return this->leftInputPadding;
}
AuUInt8 TTYConsole::GetPaddingTopOfInput()
{
return this->topInputPadding;
}
AuUInt8 TTYConsole::GetPaddingTopOfHint()
{
return this->topHintPadding;
}
AuUInt8 TTYConsole::GetPaddingTopOfHeader()
{
return this->topHeaderPadding;
}
AuUInt8 TTYConsole::GetPaddingMidOfHeader()
{
return this->midHeaderPadding;
}
AuUInt8 TTYConsole::GetPaddingBottomOfSubheader()
{
return this->bottomSubHeaderPadding;
}
AuUInt8 TTYConsole::SetPaddingHeadOfLog(AuUInt8 newValue)
{
return AuExchange(this->topLogPadding, newValue);
}
AuUInt8 TTYConsole::SetPaddingTopOfLog(AuUInt8 newValue)
{
return AuExchange(this->topLogPaddingExtra, newValue);
}
AuUInt8 TTYConsole::GetPaddingHeadOfLog()
{
return this->topLogPadding;
}
AuUInt8 TTYConsole::GetPaddingTopOfLog()
{
return this->topLogPaddingExtra;
}
static void MainTTY()
{
while (AuIsThreadRunning())
{
if (auto pLoopSource = AuConsole::StdInBufferLoopSource())
{
pLoopSource->WaitOn(1000 / 20);
}
else
{
AuThreading::Sleep(1000 / 20);
}
gTTYConsole.Pump();
}
}
void Init()
{
if (!gRuntimeConfig.console.enableConsole)
{
return;
}
gTTYConsole.Init();
if (gMultiThreadTTY && !gConsoleTTYThread)
{
gConsoleTTYThread = AuThreads::ThreadUnique(AuThreads::ThreadInfo(
AuMakeShared<AuThreads::IThreadVectorsFunctional>(AuThreads::IThreadVectorsFunctional::OnEntry_t(std::bind(MainTTY)),
AuThreads::IThreadVectorsFunctional::OnExit_t {}),
"ConsoleTTY pumper (win32 is slow as shit ok?)"
));
if (!gConsoleTTYThread)
{
gMultiThreadTTY = false;
return;
}
gConsoleTTYThread->SetThrottle(AuThreads::EThreadThrottle::eEfficient);
gConsoleTTYThread->SetPriority(AuThreads::EThreadPriority::ePrioHigh);
gConsoleTTYThread->Run();
}
}
void Exit()
{
gTTYConsole.Deinit();
}
void WriteTTYOut(const AuConsole::ConsoleMessage &msg)
{
gTTYConsole.BufferMessage(msg);
}
void WriteTTYOut(const AuConsole::ConsoleMessage &msg, const AuString &input)
{
gTTYConsole.BufferMessage(msg, input);
}
void Pump()
{
// :(
if (!gMultiThreadTTY)
{
gTTYConsole.Pump();
}
}
void PumpForce()
{
// no more force deinit for.now:
gConsoleTTYThread.reset();
gTTYConsole.Pump();
}
AUKN_SYM AuSPtr<ITTYConsole> GetTTYConsole()
{
return AuUnsafeRaiiToShared<ITTYConsole>(&gTTYConsole);
}
void OnEnter()
{
if (gTTYConsoleEnabled)
{
gTTYConsole.OnEnter();
}
}
#else
void Init()
{
}
void Pump()
{
}
void PumpForce()
{
}
void Exit()
{
}
void WriteTTYOut(const AuConsole::ConsoleMessage &msg, const AuString &input)
{
}
AUKN_SYM AuSPtr<ITTYConsole> GetTTYConsole()
{
return {};
}
void OnEnter()
{
}
#endif
}