[+] Initial unified handling of control, shift, selection, backspace handling under ConsoleTTY

This commit is contained in:
Reece Wilson 2023-02-17 19:42:30 +00:00
parent d2ad4cd10d
commit fc097de2da
2 changed files with 350 additions and 110 deletions

View File

@ -28,10 +28,270 @@ namespace Aurora::Console::ConsoleTTY
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->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()
{
this->bIsSelecting = false;
AuResetMember(this->highlightEndInBytes);
AuResetMember(this->highlightEndInPos);
AuResetMember(this->highlightStartInBytes);
AuResetMember(this->highlightStart);
}
void TTYConsoleField::WriteBuffered(TTYConsole *pConsole, AuPair<AuUInt32, AuUInt32> pos)
{
if (this->highlightStartInBytes)
{
auto sViewZero = this->inputField.substr(0, this->highlightStartInBytes.value());
auto sViewOne = 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 Offset) -> int
{
auto uIndex = this->noncanonicalCursorPosInBytes + Offset;
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);
}
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 this->inputField.size();
return false;
//return this->inputField.inputField.size();
}
void TTYConsole::Init()
@ -165,12 +425,12 @@ namespace Aurora::Console::ConsoleTTY
}
case ConsoleStd::ENoncanonicalInput::eArrowLeft:
{
NoncanonicalOnLeft();
NoncanonicalOnLeft(input.bIsShiftSequence || input.bIsAltSequence, input.bIsControlSequence);
break;
}
case ConsoleStd::ENoncanonicalInput::eArrowRight:
{
NoncanonicalOnRight();
NoncanonicalOnRight(input.bIsShiftSequence || input.bIsAltSequence, input.bIsControlSequence);
break;
}
case ConsoleStd::ENoncanonicalInput::ePageDown:
@ -186,9 +446,9 @@ namespace Aurora::Console::ConsoleTTY
case ConsoleStd::ENoncanonicalInput::eArrowDown:
{
if (this->noncanonicalCursorPosInBytes == 0 ||
if (this->inputField.noncanonicalCursorPosInBytes == 0 ||
this->GetHintLines() == 0 ||
this->noncanonicalCursorPosInBytes == this->inputField.size())
this->inputField.noncanonicalCursorPosInBytes == this->inputField.inputField.size())
{
NoncanonicalOnHistoryDown();
}
@ -202,9 +462,9 @@ namespace Aurora::Console::ConsoleTTY
case ConsoleStd::ENoncanonicalInput::eArrowUp:
{
if (this->noncanonicalCursorPosInBytes == 0 ||
if (this->inputField.noncanonicalCursorPosInBytes == 0 ||
this->GetHintLines() == 0 ||
this->noncanonicalCursorPosInBytes == this->inputField.size())
this->inputField.noncanonicalCursorPosInBytes == this->inputField.inputField.size())
{
NoncanonicalOnHistoryUp();
}
@ -236,91 +496,35 @@ namespace Aurora::Console::ConsoleTTY
void TTYConsole::NoncanonicalOnString(const AuString &input)
{
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();
inputField.AddString(input);
RedrawInput(true);
NoncanonicalSetCursor();
}
void TTYConsole::NoncanonicalOnLeft()
void TTYConsole::NoncanonicalOnLeft(bool bShift, bool bControl)
{
if (this->noncanonicalCursorPosInBytes <= 0)
{
if (this->noncanonicalCursorPosInBytes == -1)
{
NoncanonicalOnMenuLeft();
}
return;
}
int idx = AuLocale::Encoding::CountUTF8Length({this->inputField.data(), AuUInt(this->noncanonicalCursorPosInBytes - 1)}, true);
this->noncanonicalCursorPos--;
this->noncanonicalCursorPosInBytes = idx;
inputField.DirectionalArrow(-1, bShift, bControl);
NoncanonicalSetCursor();
}
void TTYConsole::NoncanonicalOnRight()
void TTYConsole::NoncanonicalOnRight(bool bShift, bool bControl)
{
if (this->noncanonicalCursorPosInBytes == -1)
{
NoncanonicalOnMenuRight();
return;
}
if (this->noncanonicalCursorPosInBytes == this->inputField.size())
{
return;
}
auto uAddend = AuLocale::Encoding::IterateUTF8({ &this->inputField[this->noncanonicalCursorPosInBytes], this->inputField.size() - this->noncanonicalCursorPosInBytes });
this->noncanonicalCursorPos++;
this->noncanonicalCursorPosInBytes += uAddend;
inputField.DirectionalArrow(1, bShift, bControl);
NoncanonicalSetCursor();
}
void TTYConsole::NoncanonicalOnBackspace()
{
if (noncanonicalCursorPosInBytes <= 0)
if (!inputField.Backspace())
{
return;
}
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;
RedrawInput(true);
NoncanonicalSetCursor();
}
bool TTYConsole::IsWin32UxMode()
{
return true;
@ -427,26 +631,26 @@ namespace Aurora::Console::ConsoleTTY
{
AU_LOCK_GUARD(this->historyLock->AsWritable());
if (this->inputField.size())
if (this->inputField.inputField.size())
{
#if defined(AURORA_IS_MODERNNT_DERIVED)
if (this->inputField == "!s")
if (this->inputField.inputField == "!s")
{
EnterScrollMode();
}
else if (this->inputField == "!c")
else if (this->inputField.inputField == "!c")
{
LeaveScrollMode();
}
else
#endif
if (this->inputField == "!b")
if (this->inputField.inputField == "!b")
{
this->iScrollPos = -1;
this->bTriggerRedraw = true;
}
else if (this->inputField == "!t")
else if (this->inputField.inputField == "!t")
{
this->iScrollPos = 0;
this->bTriggerRedraw = true;
@ -454,31 +658,29 @@ namespace Aurora::Console::ConsoleTTY
else
{
#if defined(AURORA_IS_MODERNNT_DERIVED)
if (this->inputField == "help")
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 == "help")
if (this->inputField.inputField == "help")
{
AuLogInfo("ConsoleTTY: Hold control + arrow key up/down to scroll.");
}
#endif
if (this->inputField == "help")
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);
AuConsole::DispatchRawLine(this->inputField.inputField);
}
}
AuTryInsert(this->history, this->inputField);
AuTryInsert(this->history, this->inputField.inputField);
this->noncanonicalCursorPos = 0;
this->noncanonicalCursorPosInBytes = 0;
this->inputField.clear();
this->inputField.Clear();
OnEnter();
}
@ -510,8 +712,8 @@ namespace Aurora::Console::ConsoleTTY
return;
}
if (this->noncanonicalCursorPos == 0 ||
noncanonicalCursorPosInBytes == this->inputField.size())
if (this->inputField.noncanonicalCursorPos == 0 ||
this->inputField.noncanonicalCursorPosInBytes == this->inputField.inputField.size())
{
locked = true;
}
@ -529,13 +731,13 @@ namespace Aurora::Console::ConsoleTTY
}
this->inputField = this->history[this->iHistoryPos];
this->inputField.inputField = this->history[this->iHistoryPos];
HistoryUpdateCursor();
if (locked)
{
this->noncanonicalCursorPos = GuessWidth(this->inputField);
this->noncanonicalCursorPosInBytes = (int)this->inputField.size();
this->inputField.noncanonicalCursorPos = GuessWidth(this->inputField.inputField);
this->inputField.noncanonicalCursorPosInBytes = (int)this->inputField.inputField.size();
NoncanonicalSetCursor();
}
}
@ -551,8 +753,8 @@ namespace Aurora::Console::ConsoleTTY
return;
}
if (this->noncanonicalCursorPos == 0 ||
noncanonicalCursorPosInBytes == this->inputField.size())
if (this->inputField.noncanonicalCursorPos == 0 ||
this->inputField.noncanonicalCursorPosInBytes == this->inputField.inputField.size())
{
locked = true;
}
@ -567,27 +769,27 @@ namespace Aurora::Console::ConsoleTTY
if (this->iHistoryPos >= this->history.size())
{
this->iHistoryPos = -1;
this->inputField.clear();
this->inputField.inputField.clear();
}
else
{
this->inputField = this->history[this->iHistoryPos];
this->inputField.inputField = this->history[this->iHistoryPos];
}
HistoryUpdateCursor();
if (locked)
{
this->noncanonicalCursorPos = GuessWidth(this->inputField);
this->noncanonicalCursorPosInBytes = (int)this->inputField.size();
this->inputField.noncanonicalCursorPos = GuessWidth(this->inputField.inputField);
this->inputField.noncanonicalCursorPosInBytes = (int)this->inputField.inputField.size();
NoncanonicalSetCursor();
}
}
void TTYConsole::HistoryUpdateCursor()
{
this->noncanonicalCursorPos = AuMin<AuUInt32>(this->noncanonicalCursorPos, GuessWidth(this->inputField));
this->noncanonicalCursorPosInBytes = AuMin<AuUInt32>(this->noncanonicalCursorPosInBytes, AuUInt32(this->inputField.size()));
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();
}
@ -759,7 +961,7 @@ namespace Aurora::Console::ConsoleTTY
{
this->iHistoryPos = -1;
auto line = this->currentHeight;
this->inputField.clear();
this->inputField.Clear();
this->RedrawInput(true);
this->NoncanonicalSetCursor();
}
@ -993,7 +1195,7 @@ namespace Aurora::Console::ConsoleTTY
bool bRedrawWindowRequired = this->bTriggerRedraw;
bool bHasMesasgesPending = messagesPending.size() || this->bTriggerRedraw;
auto currentHash = AuFnv1a64Runtime(inputField.data(), inputField.size());
auto currentHash = AuFnv1a64Runtime(inputField.inputField.data(), inputField.inputField.size());
bool bInputChanged = NoncanonicalMode() && (lastInputHash != currentHash && lastInputHash != 0);
lastInputHash = currentHash;
@ -1397,11 +1599,7 @@ namespace Aurora::Console::ConsoleTTY
WriteBuffered({}, end);
}
AuUInt32 TTYConsole::GuessWidth(const AuString &referenceLine)
{
return AuUInt32(AuLocale::ConvertFromUTF8(referenceLine).size());
}
const AuString &TTYConsole::Stringify(const AuString &referenceLine, const AlignedString &string, bool spacing, bool brackets, bool extraPadding)
{
if (string.str.empty())
@ -1717,7 +1915,7 @@ namespace Aurora::Console::ConsoleTTY
if (clear && NoncanonicalMode())
{
WriteBuffered({this->GetLeftBorder() + this->leftPadding + 1, height}, this->inputField);
this->inputField.WriteBuffered(this, { this->GetLeftBorder() + this->leftPadding + 1, height });
}
}
@ -1941,7 +2139,7 @@ namespace Aurora::Console::ConsoleTTY
return {this->GetLeftBorder() + this->leftPadding + 1 + this->leftInputPadding, this->currentHeight - (1 + this->GetBottomBorder())};
}
return {this->GetLeftBorder() + this->leftPadding + 1 + this->leftInputPadding + (noncanonicalCursorPos == -1 ? 0 : noncanonicalCursorPos) /*:(*/, this->currentHeight - (1 + this->GetBottomBorder())}; // { i do not like this, ...}
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()

View File

@ -14,8 +14,44 @@ namespace Aurora::Console::ConsoleTTY
void EnterScrollMode();
void LeaveScrollMode();
struct TTYConsole;
struct TTYConsoleField
{
TTYConsoleField(TTYConsole *pParent);
AuString inputField;
int noncanonicalCursorPos {};
int noncanonicalCursorPosInBytes {};
void AddString(const AuString &str);
bool Backspace();
void DirectionalArrow(int iDirection, bool bShift, bool bControl);
void Clear();
void WriteBuffered(TTYConsole *pConsole, AuPair<AuUInt32, AuUInt32> pos);
AuOptionalEx<int> highlightStartInBytes {};
AuOptionalEx<int> highlightStart {};
AuOptionalEx<int> highlightEndInBytes {};
AuOptionalEx<int> highlightEndInPos {};
int highlightChars {};
void DoUnselectTick();
private:
void Left();
void Right();
TTYConsole *pParent;
bool bIsSelecting {};
int iLastDirection {};
};
struct TTYConsole : ITTYConsole
{
TTYConsole();
void BufferMessage(const AuConsole::ConsoleMessage &msg) override;
void BufferMessage(const AuConsole::ConsoleMessage &msg, const AuString &input) ;
@ -33,6 +69,11 @@ namespace Aurora::Console::ConsoleTTY
bool uxModeFlipped {};
inline void SignalRedraw()
{
this->bTriggerRedraw = true;
}
private:
bool UTF8();
@ -60,8 +101,8 @@ namespace Aurora::Console::ConsoleTTY
void NoncanonicalOnString(const AuString &input);
void NoncanonicalOnBackspace();
void NoncanonicalOnEnter();
void NoncanonicalOnLeft();
void NoncanonicalOnRight();
void NoncanonicalOnLeft(bool bShift, bool bControl);
void NoncanonicalOnRight(bool bShift, bool bControl);
void NoncanonicalOnPageUp();
void NoncanonicalOnPageDown();
@ -84,8 +125,9 @@ namespace Aurora::Console::ConsoleTTY
AuString historyFileName;
int noncanonicalCursorPos {};
int noncanonicalCursorPosInBytes {};
TTYConsoleField inputField;
//int noncanonicalCursorPos {};
//int noncanonicalCursorPosInBytes {};
virtual int GetBannerLines();
virtual int GetLogBoxLines();
@ -170,6 +212,7 @@ namespace Aurora::Console::ConsoleTTY
private:
friend struct TTYConsoleField;
void DebugLogArea();
AuList<AuPair<AuString /*autocompletion*/, AuList<AuString/* secondary hint*/>>> hintStrings;
@ -209,7 +252,6 @@ namespace Aurora::Console::ConsoleTTY
AuString tempMemory;
AuUInt32 GuessWidth(const AuString &referenceLine);
const AuString &Stringify(const AuString &referenceLine, const AlignedString &string, bool spacing, bool brackets, bool extraPadding);
@ -237,7 +279,7 @@ namespace Aurora::Console::ConsoleTTY
void UXModeFlip();
void UXModeStart();
AuString inputField {};
//AuString inputField {};
AuUInt32 lastInputHash {};
AuUInt32 lastBorderHash {};