Improve ANSI escape codes support

This commit is contained in:
John Arbuckle
2025-04-15 11:02:06 -04:00
parent 531a03fc59
commit 4fb58eaf23
2 changed files with 180 additions and 24 deletions
+170 -24
View File
@@ -25,8 +25,10 @@
#include "MacUtils.h"
#include "Fonts.h"
#include "Processes.h"
#include <cctype>
#include <algorithm>
#include <stack>
#include <sstream>
using namespace retro;
@@ -131,11 +133,13 @@ namespace
Console::Console()
{
InitEscapeSequenceMap();
}
Console::Console(GrafPtr port, Rect r)
{
Init(port, r);
InitEscapeSequenceMap();
}
Console::~Console()
@@ -270,6 +274,7 @@ void Console::ScrollUp(short n)
bool Console::ProcessEscSequence(char c)
{
static std::string argument;
switch(sequenceState)
{
case State::noSequence:
@@ -283,31 +288,15 @@ bool Console::ProcessEscSequence(char c)
sequenceState=State::noSequence; // Unrecognized sequence
break;
case State::waitingForControlSequence:
sequenceState=State::waitingForM;
switch(c)
{
case '0': // Normal character
currentAttr.reset();
break;
case '1': // Bold
currentAttr.setBold(true);
break;
case '3': // Italic
currentAttr.setItalic(true);
break;
case '4': // Underline
currentAttr.setUnderline(true);
break;
default:
sequenceState=State::noSequence; // Unrecognized sequence
if (isalpha(c)) {
auto escFunc = escapeSequenceMap.at(c);
escFunc(argument);
sequenceState=State::noSequence;
argument = "";
} else {
argument = argument + c;
}
break;
case State::waitingForM:
if(c=='m')
sequenceState=State::noSequence; // Normal end of sequence
else
sequenceState=State::noSequence; // Unrecognized sequence (but we end it anyway!)
break;
case State::waitingForOSCStart:
if(c=='0')
sequenceState=State::waitingForSemicolon;
@@ -561,3 +550,160 @@ char Console::WaitNextChar()
{
return 0;
}
// Map a letter to a function
void Console::InitEscapeSequenceMap()
{
escapeSequenceMap.insert({'H', [&](std::string args) { Console::SetCursorPosition(args); } });
escapeSequenceMap.insert({'J', [&](std::string args) { EraseInDisplay(args); } });
escapeSequenceMap.insert({'m', [&](std::string args) { SetDisplayAttributes(args); } });
}
// turns an argument string into numbers
// example: "12;13" would return a stack with numbers 13 and 12
std::stack<int> parseArguments(std::string str) {
std::istringstream iss(str);
std::string token;
std::stack<int> numberStack;
while (getline(iss, token, ';'))
{
if(token == "")
{
numberStack.push(1);
}
else
{
numberStack.push(atoi(token.c_str()));
}
}
if (numberStack.empty())
{
numberStack.push(1);
numberStack.push(1);
}
if (numberStack.size() == 1)
{
numberStack.push(1);
}
return numberStack;
}
// Bound to ANSI escape code H
// Page: https://en.wikipedia.org/wiki/ANSI_escape_code
// Section: Control Sequence Introducer commands
// Name: Cursor Position
void Console::SetCursorPosition(std::string args)
{
// possible formats the arguments can be in:
// n -> (1,n)
// n;m. -> (m,n)
// n;m; -> (m,n)
// n; -> (1,n)
// ;m -> (m,1)
// -> (1,1)
auto numberStack = parseArguments(args);
cursorX = numberStack.top();
numberStack.pop();
cursorY = numberStack.top();
Update();
}
// Bound to ANSI escape code J
// Page: https://en.wikipedia.org/wiki/ANSI_escape_code
// Section: Control Sequence Introducer commands
// Name: Erase in Display
void Console::EraseInDisplay(std::string args)
{
int n;
if (args == "")
n = 0;
else
n = atoi(args.c_str());
switch(n) {
case 0: // clear from cursor to end of window
ClearFromCursorToEndOfWindow();
break;
case 1: // clear from cursor to beginning of the window
ClearFromTopOfWindowToCursor();
break;
case 2: // clear entire screen
ClearWindow();
break;
case 3: // clear entire screen and delete all lines saved in the scrollback buffer
ClearWindow();
break;
}
}
// Sets attributes of characters
// Note: only a few attributes are currently implemented
// Bound to ANSI escape code m
// Page: https://en.wikipedia.org/wiki/ANSI_escape_code
// Section: Select Graphic Rendition parameters
void Console::SetDisplayAttributes(std::string args)
{
char c = args.c_str()[0];
switch(c)
{
case '0': // Normal character
currentAttr.reset();
break;
case '1': // Bold
currentAttr.setBold(true);
break;
case '3': // Italic
currentAttr.setItalic(true);
break;
case '4': // Underline
currentAttr.setUnderline(true);
break;
}
}
// Clears the window of all text
void Console::ClearWindow()
{
// Fill the buffer with blank spaces
std::fill(chars.begin(), chars.end(), AttributedChar(' ', currentAttr));
std::fill(onscreen.begin(), onscreen.end(), AttributedChar(' ', currentAttr));
// Actually erase the window
EraseRect(&bounds);
Update();
}
// Clears the window of text from the current cursor position to the bottom of the window
void Console::ClearFromCursorToEndOfWindow()
{
int newPosition = cursorY * cols + cursorX;
// Fill the buffer with blank spaces
std::fill(chars.begin() + newPosition, chars.end(), AttributedChar(' ', currentAttr));
std::fill(onscreen.begin() + newPosition, onscreen.end(), AttributedChar(' ', currentAttr));
// Actually erase the window
EraseRect(&bounds);
Update();
}
// Clears the window from the top to the current cursor position
void Console::ClearFromTopOfWindowToCursor()
{
int newPosition = cursorY * cols + cursorX;
// Fill the buffer with blank spaces
std::fill(chars.begin(), chars.begin() + newPosition, AttributedChar(' ', currentAttr));
std::fill(onscreen.begin(), onscreen.begin() + newPosition, AttributedChar(' ', currentAttr));
// Actually erase the window
EraseRect(&bounds);
Update();
}
+10
View File
@@ -26,6 +26,8 @@
#include <Quickdraw.h>
#include <vector>
#include <string>
#include <map>
#include <functional>
namespace retro
{
@@ -105,6 +107,7 @@ namespace retro
GrafPtr consolePort = nullptr;
Rect bounds;
Attributes currentAttr;
std::map<char, std::function<void(std::string)>> escapeSequenceMap;
std::vector<AttributedChar> chars, onscreen;
@@ -136,6 +139,13 @@ namespace retro
void InvalidateCursor();
virtual char WaitNextChar();
void InitEscapeSequenceMap();
void SetCursorPosition(std::string);
void EraseInDisplay(std::string);
void SetDisplayAttributes(std::string);
void ClearWindow();
void ClearFromCursorToEndOfWindow();
void ClearFromTopOfWindowToCursor();
protected:
void Init(GrafPtr port, Rect r);