2553 lines
68 KiB
C++
2553 lines
68 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)
|
|
#if defined(AURORA_PLATFORM_WIN32)
|
|
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();
|
|
}
|
|
|
|
if (AuSwInfo::IsWindows11OrGreater())
|
|
{
|
|
AuLogWarn("Aurora's ConsoleTTY *logger* does not run under Windows Terminal. You must use conhost via cmd.exe or upgrade to Windows 7 or whichever version of 10/LTSC has the best drivers.");
|
|
}
|
|
#endif
|
|
|
|
return true;
|
|
}
|
|
|
|
void TTYConsole::End()
|
|
{
|
|
//#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
#if defined(AURORA_PLATFORM_WIN32)
|
|
if (pSetConsoleActiveScreenBuffer)
|
|
{
|
|
pSetConsoleActiveScreenBuffer(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)
|
|
#if defined(AURORA_PLATFORM_WIN32)
|
|
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: !
|
|
|
|
if (auto pEvent = ConsoleStd::GetConTTYEvent())
|
|
{
|
|
pEvent->Set();
|
|
}
|
|
}
|
|
|
|
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: !
|
|
|
|
if (auto pEvent = ConsoleStd::GetConTTYEvent())
|
|
{
|
|
pEvent->Set();
|
|
}
|
|
}
|
|
|
|
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;
|
|
|
|
// Paranoia
|
|
{
|
|
static AuInitOnce gInitOnce;
|
|
gInitOnce.Call([]()
|
|
{
|
|
atexit([]()
|
|
{
|
|
LeaveScrollMode();
|
|
Exit();
|
|
});
|
|
});
|
|
}
|
|
#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
|
|
#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()
|
|
{
|
|
AuString line;
|
|
|
|
{
|
|
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.");
|
|
}
|
|
|
|
line = this->inputField.inputField;
|
|
}
|
|
|
|
AuTryInsert(this->history, this->inputField.inputField);
|
|
}
|
|
|
|
this->inputField.Clear();
|
|
|
|
OnEnter();
|
|
}
|
|
|
|
AuConsole::DispatchRawLine(line);
|
|
}
|
|
|
|
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 AuROString &in)
|
|
{
|
|
this->history.push_back(AuString(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, AuString(in)));
|
|
#endif
|
|
|
|
if (in.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
int XOffset {};
|
|
XOffset = GetLeftBorder();
|
|
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
DWORD idc;
|
|
auto line2 = AuLocale::ConvertFromUTF8(in);
|
|
pSetConsoleCursorPosition(GetTTYHandle(), COORD { AuStaticCast<short>(XOffset), AuStaticCast<short>(Y) });
|
|
pWriteConsoleW(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, AuString(in)));
|
|
#endif
|
|
|
|
if (in.empty())
|
|
{
|
|
return;
|
|
}
|
|
|
|
#if defined(AURORA_IS_MODERNNT_DERIVED)
|
|
DWORD idc;
|
|
auto line2 = AuLocale::ConvertFromUTF8(in);
|
|
pSetConsoleCursorPosition(GetTTYHandle(), COORD { AuStaticCast<short>(pos.first), AuStaticCast<short>(pos.second) });
|
|
pWriteConsoleW(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, AuString(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);
|
|
pSetConsoleTextAttribute(hConsole, attrib);
|
|
pSetConsoleCursorPosition(GetTTYHandle(), COORD { AuStaticCast<short>(pos.first), AuStaticCast<short>(pos.second) });
|
|
pWriteConsoleW(hConsole, line2.data(), AuUInt32(line2.size()), &idc, NULL);
|
|
pSetConsoleTextAttribute(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 AuROString &newValue)
|
|
{
|
|
this->bTriggerRedraw = true;
|
|
return AuExchange(this->headerTitle.str, newValue);
|
|
}
|
|
|
|
AuString TTYConsole::SetLogBoxBorderTitle(const AuROString &newValue)
|
|
{
|
|
this->bTriggerRedraw = true;
|
|
return AuExchange(this->logTitle.str, newValue);
|
|
}
|
|
|
|
AuString TTYConsole::SetHeader(const AuROString &newValue)
|
|
{
|
|
this->bTriggerRedraw = true;
|
|
return AuExchange(this->header.str, newValue);
|
|
}
|
|
|
|
AuString TTYConsole::SetSubHeader(const AuROString &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 0
|
|
if (auto pLoopSource = AuConsole::StdInBufferLoopSource())
|
|
{
|
|
pLoopSource->WaitOn(1000 / 20);
|
|
}
|
|
else
|
|
{
|
|
AuThreading::Sleep(1000 / 20);
|
|
}
|
|
#else
|
|
if (auto pLoopSource = ConsoleStd::GetLoopSourceConTTY())
|
|
{
|
|
if (gTTYConsole.uxModeFlipped)
|
|
{
|
|
pLoopSource->WaitOn(5000);
|
|
}
|
|
else
|
|
{
|
|
pLoopSource->WaitOn(500);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
AuThreading::Sleep(1000 / 20);
|
|
}
|
|
#endif
|
|
try
|
|
{
|
|
gTTYConsole.Pump();
|
|
}
|
|
catch (...)
|
|
{
|
|
SysPushErrorCatch();
|
|
}
|
|
}
|
|
}
|
|
|
|
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
|
|
|
|
} |