mirror of
https://github.com/epasveer/seer.git
synced 2026-08-29 16:40:42 +08:00
397 lines
14 KiB
C++
397 lines
14 KiB
C++
// SPDX-FileCopyrightText: 2021 Ernie Pasveer <epasveer@att.net>
|
|
//
|
|
// SPDX-License-Identifier: MIT
|
|
|
|
#include "QAnsiTextEdit.h"
|
|
#include <QtCore/QDebug>
|
|
|
|
//
|
|
//
|
|
//
|
|
QAnsiTextEditFormattedText::QAnsiTextEditFormattedText(const QString& txt, const QTextCharFormat& fmt) {
|
|
text = txt;
|
|
format = fmt;
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
QList<QAnsiTextEditFormattedText> QAnsiTextEditEscapeCodeHandler::parseText (const QAnsiTextEditFormattedText& input) {
|
|
|
|
enum AnsiEscapeCodes {
|
|
ResetFormat = 0,
|
|
BoldText = 1,
|
|
UnderLinedText = 4,
|
|
TextColorStart = 30,
|
|
TextColorEnd = 37,
|
|
RgbTextColor = 38,
|
|
DefaultTextColor = 39,
|
|
BackgroundColorStart = 40,
|
|
BackgroundColorEnd = 47,
|
|
RgbBackgroundColor = 48,
|
|
DefaultBackgroundColor = 49
|
|
};
|
|
|
|
const QString escape = "\x1b[";
|
|
const QChar semicolon = ';';
|
|
const QChar colorTerminator = 'm';
|
|
const QChar eraseToEol = 'K';
|
|
|
|
QList<QAnsiTextEditFormattedText> outputData;
|
|
QTextCharFormat charFormat = _previousFormatClosed ? input.format : _previousFormat;
|
|
QString strippedText;
|
|
|
|
if (_pendingText.isEmpty()) {
|
|
strippedText = input.text;
|
|
}else{
|
|
strippedText = _pendingText.append(input.text);
|
|
_pendingText.clear();
|
|
}
|
|
|
|
while (!strippedText.isEmpty()) {
|
|
if (_waitingForTerminator) {
|
|
// We ignore all escape codes taking string arguments.
|
|
QString terminator = "\x1b\\";
|
|
int terminatorPos = strippedText.indexOf(terminator);
|
|
if (terminatorPos == -1 && !_alternateTerminator.isEmpty()) {
|
|
terminator = _alternateTerminator;
|
|
terminatorPos = strippedText.indexOf(terminator);
|
|
}
|
|
if (terminatorPos == -1) {
|
|
_pendingText = strippedText;
|
|
break;
|
|
}
|
|
_waitingForTerminator = false;
|
|
_alternateTerminator.clear();
|
|
strippedText.remove(0, terminatorPos + terminator.length());
|
|
if (strippedText.isEmpty()) {
|
|
break;
|
|
}
|
|
}
|
|
|
|
const int escapePos = strippedText.indexOf(escape.at(0));
|
|
if (escapePos < 0) {
|
|
outputData << QAnsiTextEditFormattedText(strippedText, charFormat);
|
|
break;
|
|
}else if (escapePos != 0) {
|
|
outputData << QAnsiTextEditFormattedText(strippedText.left(escapePos), charFormat);
|
|
strippedText.remove(0, escapePos);
|
|
}
|
|
|
|
while (!strippedText.isEmpty() && escape.at(0) == strippedText.at(0)) {
|
|
|
|
if (escape.startsWith(strippedText)) {
|
|
// control secquence is not complete
|
|
_pendingText += strippedText;
|
|
strippedText.clear();
|
|
break;
|
|
}
|
|
|
|
if (!strippedText.startsWith(escape)) {
|
|
switch (strippedText.at(1).toLatin1()) {
|
|
case '\\': // Unexpected terminator sequence.
|
|
Q_FALLTHROUGH();
|
|
case 'N': case 'O': // Ignore unsupported single-character sequences.
|
|
strippedText.remove(0, 2);
|
|
break;
|
|
case ']':
|
|
_alternateTerminator = QChar(7);
|
|
Q_FALLTHROUGH();
|
|
case 'P': case 'X': case '^': case '_':
|
|
strippedText.remove(0, 2);
|
|
_waitingForTerminator = true;
|
|
break;
|
|
default:
|
|
// not a control sequence
|
|
_pendingText.clear();
|
|
outputData << QAnsiTextEditFormattedText(strippedText.left(1), charFormat);
|
|
strippedText.remove(0, 1);
|
|
continue;
|
|
}
|
|
break;
|
|
}
|
|
|
|
_pendingText += strippedText.mid(0, escape.length());
|
|
strippedText.remove(0, escape.length());
|
|
|
|
// \e[K is not supported. Just strip it.
|
|
if (strippedText.startsWith(eraseToEol)) {
|
|
_pendingText.clear();
|
|
strippedText.remove(0, 1);
|
|
continue;
|
|
}
|
|
|
|
// get the number
|
|
QString strNumber;
|
|
QStringList numbers;
|
|
|
|
while (!strippedText.isEmpty()) {
|
|
if (strippedText.at(0).isDigit()) {
|
|
strNumber += strippedText.at(0);
|
|
}else{
|
|
if (!strNumber.isEmpty()) {
|
|
numbers << strNumber;
|
|
}
|
|
if (strNumber.isEmpty() || strippedText.at(0) != semicolon) {
|
|
break;
|
|
}
|
|
strNumber.clear();
|
|
}
|
|
_pendingText += strippedText.mid(0, 1);
|
|
strippedText.remove(0, 1);
|
|
}
|
|
|
|
if (strippedText.isEmpty()) {
|
|
break;
|
|
}
|
|
|
|
// remove terminating char
|
|
if (!strippedText.startsWith(colorTerminator)) {
|
|
_pendingText.clear();
|
|
strippedText.remove(0, 1);
|
|
break;
|
|
}
|
|
|
|
// got consistent control sequence, ok to clear pending text
|
|
_pendingText.clear();
|
|
strippedText.remove(0, 1);
|
|
|
|
if (numbers.isEmpty()) {
|
|
charFormat = input.format;
|
|
endFormatScope();
|
|
}
|
|
|
|
for (int i = 0; i < numbers.size(); ++i) {
|
|
|
|
const uint code = numbers.at(i).toUInt();
|
|
|
|
if (code >= TextColorStart && code <= TextColorEnd) {
|
|
//qDebug() << "TextColorStart/TextColorEnd called";
|
|
charFormat.setForeground(ansiColor(code - TextColorStart));
|
|
setFormatScope(charFormat);
|
|
}else if (code >= BackgroundColorStart && code <= BackgroundColorEnd) {
|
|
//qDebug() << "BackgroundColorStart/BackgroundColorEnd called";
|
|
charFormat.setBackground(ansiColor(code - BackgroundColorStart));
|
|
setFormatScope(charFormat);
|
|
}else{
|
|
switch (code) {
|
|
case ResetFormat:
|
|
//qDebug() << "ResetFormat called";
|
|
charFormat.setFontWeight(QFont::Normal);
|
|
charFormat.setFontUnderline(false);
|
|
charFormat.setForeground(input.format.foreground());
|
|
charFormat.setBackground(input.format.background());
|
|
setFormatScope(charFormat);
|
|
endFormatScope();
|
|
break;
|
|
case BoldText:
|
|
//qDebug() << "BoldText called";
|
|
charFormat.setFontWeight(QFont::ExtraBold);
|
|
setFormatScope(charFormat);
|
|
break;
|
|
case UnderLinedText:
|
|
//qDebug() << "UnderLinedText called";
|
|
charFormat.setFontUnderline(true);
|
|
setFormatScope(charFormat);
|
|
break;
|
|
case DefaultTextColor:
|
|
//qDebug() << "DefaultTextColor called";
|
|
charFormat.setForeground(input.format.foreground());
|
|
setFormatScope(charFormat);
|
|
break;
|
|
case DefaultBackgroundColor:
|
|
//qDebug() << "DefaultBackgroundColor called";
|
|
charFormat.setBackground(input.format.background());
|
|
setFormatScope(charFormat);
|
|
break;
|
|
case RgbTextColor:
|
|
case RgbBackgroundColor:
|
|
//qDebug() << "RgbTextColor/RgbBackgroundColor called";
|
|
// See http://en.wikipedia.org/wiki/ANSI_escape_code#Colors
|
|
if (++i >= numbers.size()) {
|
|
break;
|
|
}
|
|
|
|
switch (numbers.at(i).toInt()) {
|
|
case 2:
|
|
// RGB set with format: 38;2;<r>;<g>;<b>
|
|
if ((i + 3) < numbers.size()) {
|
|
(code == RgbTextColor) ?
|
|
charFormat.setForeground(QColor(numbers.at(i + 1).toInt(),
|
|
numbers.at(i + 2).toInt(),
|
|
numbers.at(i + 3).toInt())) :
|
|
charFormat.setBackground(QColor(numbers.at(i + 1).toInt(),
|
|
numbers.at(i + 2).toInt(),
|
|
numbers.at(i + 3).toInt()));
|
|
setFormatScope(charFormat);
|
|
}
|
|
i += 3;
|
|
break;
|
|
case 5:
|
|
// 256 color mode with format: 38;5;<i>
|
|
uint index = numbers.at(i + 1).toUInt();
|
|
|
|
QColor color;
|
|
if (index < 8) {
|
|
// The first 8 colors are standard low-intensity ANSI colors.
|
|
color = ansiColor(index);
|
|
}else if (index < 16) {
|
|
// The next 8 colors are standard high-intensity ANSI colors.
|
|
color = ansiColor(index - 8).lighter(150);
|
|
}else if (index < 232) {
|
|
// The next 216 colors are a 6x6x6 RGB cube.
|
|
uint o = index - 16;
|
|
color = QColor((o / 36) * 51, ((o / 6) % 6) * 51, (o % 6) * 51);
|
|
}else{
|
|
// The last 24 colors are a greyscale gradient.
|
|
int grey = int((index - 232) * 11);
|
|
color = QColor(grey, grey, grey);
|
|
}
|
|
|
|
if (code == RgbTextColor) {
|
|
charFormat.setForeground(color);
|
|
}else{
|
|
charFormat.setBackground(color);
|
|
}
|
|
|
|
setFormatScope(charFormat);
|
|
++i;
|
|
break;
|
|
}
|
|
break;
|
|
default:
|
|
qDebug() << "Unkown code:" << code;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return outputData;
|
|
}
|
|
|
|
void QAnsiTextEditEscapeCodeHandler::endFormatScope () {
|
|
_previousFormatClosed = true;
|
|
}
|
|
|
|
void QAnsiTextEditEscapeCodeHandler::setFormatScope (const QTextCharFormat& charFormat) {
|
|
_previousFormat = charFormat;
|
|
_previousFormatClosed = false;
|
|
}
|
|
|
|
QTextCharFormat QAnsiTextEditEscapeCodeHandler::formatScope () const {
|
|
return _previousFormat;
|
|
}
|
|
|
|
QColor QAnsiTextEditEscapeCodeHandler::ansiColor(uint code) {
|
|
|
|
const int red = code & 1 ? 170 : 0;
|
|
const int green = code & 2 ? 170 : 0;
|
|
const int blue = code & 4 ? 170 : 0;
|
|
|
|
return QColor(red, green, blue);
|
|
}
|
|
|
|
//
|
|
//
|
|
//
|
|
QAnsiTextEdit::QAnsiTextEdit (QWidget* parent) : QPlainTextEdit(parent) {
|
|
}
|
|
|
|
QAnsiTextEdit::QAnsiTextEdit (const QString& text, QWidget* parent) : QPlainTextEdit(parent) {
|
|
|
|
setAnsiText(text);
|
|
}
|
|
|
|
QAnsiTextEdit::~QAnsiTextEdit () {
|
|
}
|
|
|
|
void QAnsiTextEdit::dumpCharFormat (QString string, QTextCharFormat format) {
|
|
|
|
qDebug() << "dumpCharFormat" << string << "Font" << format.font().toString();
|
|
qDebug() << "dumpCharFormat" << string << "Color" << format.foreground().color();
|
|
qDebug() << "dumpCharFormat" << string << "FontWeight" << format.fontWeight();
|
|
qDebug() << "dumpCharFormat" << string << "UnderLined" << format.fontUnderline();
|
|
qDebug() << "";
|
|
}
|
|
|
|
void QAnsiTextEdit::setAnsiText (const QString& text) {
|
|
|
|
// Reset the text edit
|
|
clear();
|
|
|
|
// Use append method.
|
|
appendAnsiText(text);
|
|
}
|
|
|
|
void QAnsiTextEdit::appendAnsiText (const QString& text) {
|
|
|
|
//
|
|
// Use the 'append' method for the first sub text, then
|
|
// the 'insert' method for every sub text after that.
|
|
//
|
|
|
|
// Save current TextCharFormat
|
|
QTextCharFormat saveFormat = currentCharFormat();
|
|
|
|
// Create the default text object;
|
|
QAnsiTextEditFormattedText ftext;
|
|
ftext.text = text;
|
|
ftext.format = currentCharFormat();
|
|
|
|
// Parse it. A list of sub text objects is created.
|
|
QList<QAnsiTextEditFormattedText> ftexts = _escapeCodeHandler.parseText(ftext);
|
|
|
|
// Print each sub text object. Each one has its own text format.
|
|
bool first = true;
|
|
|
|
for (const QAnsiTextEditFormattedText& ftext : ftexts) {
|
|
|
|
QTextCharFormat format = ftext.format;
|
|
|
|
setCurrentCharFormat(format);
|
|
|
|
if (first) {
|
|
appendPlainText(ftext.text);
|
|
first = false;
|
|
}else{
|
|
insertPlainText(ftext.text);
|
|
}
|
|
}
|
|
|
|
// Restore current TextCharFormat
|
|
setCurrentCharFormat(saveFormat);
|
|
}
|
|
|
|
void QAnsiTextEdit::insertAnsiText (const QString& text) {
|
|
|
|
//
|
|
// Use the 'insert' method for every sub text.
|
|
//
|
|
|
|
// Save current TextCharFormat
|
|
QTextCharFormat saveFormat = currentCharFormat();
|
|
|
|
// Create the default text object;
|
|
QAnsiTextEditFormattedText ftext;
|
|
ftext.text = text;
|
|
ftext.format = currentCharFormat();
|
|
|
|
// Parse it. A list of sub text objects is created.
|
|
QList<QAnsiTextEditFormattedText> ftexts = _escapeCodeHandler.parseText(ftext);
|
|
|
|
// Print each sub text object. Each one has its own text format.
|
|
for (const QAnsiTextEditFormattedText& ftext : ftexts) {
|
|
|
|
QTextCharFormat format = ftext.format;
|
|
|
|
setCurrentCharFormat(format);
|
|
insertPlainText(ftext.text);
|
|
}
|
|
|
|
// Restore current TextCharFormat
|
|
setCurrentCharFormat(saveFormat);
|
|
}
|
|
|