UI: colored ANSI logs fix-ups

This commit is contained in:
zeph 2026-01-09 18:33:16 +01:00 committed by Megamouse
parent b2768bbd61
commit 484d97caf1
5 changed files with 68 additions and 48 deletions

View file

@ -132,8 +132,6 @@ log_frame::log_frame(std::shared_ptr<gui_settings> _gui_settings, QWidget* paren
m_tty->setContextMenuPolicy(Qt::CustomContextMenu);
m_tty->document()->setMaximumBlockCount(max_block_count_tty);
m_tty->installEventFilter(this);
m_tty_ansi_highlighter = new AnsiHighlighter(m_tty->document());
m_tty_input = new QLineEdit();
if (m_tty_channel >= 0)
@ -164,6 +162,11 @@ log_frame::log_frame(std::shared_ptr<gui_settings> _gui_settings, QWidget* paren
CreateAndConnectActions();
LoadSettings();
if (m_ansi_tty)
{
m_tty_ansi_highlighter = new AnsiHighlighter(m_tty->document());
}
m_timer = new QTimer(this);
connect(m_timer, &QTimer::timeout, this, &log_frame::UpdateUI);
}
@ -291,6 +294,16 @@ void log_frame::CreateAndConnectActions()
{
m_gui_settings->SetValue(gui::l_ansi_code, checked);
m_ansi_tty = checked;
if (!m_tty_ansi_highlighter)
{
m_tty_ansi_highlighter = new AnsiHighlighter(m_tty->document());
}
else
{
delete m_tty_ansi_highlighter;
m_tty_ansi_highlighter = nullptr;
}
});
m_tty_channel_acts = new QActionGroup(this);
@ -602,13 +615,15 @@ void log_frame::UpdateUI()
buf_line.assign(std::string_view(m_tty_buf).substr(str_index, m_tty_buf.find_first_of('\n', str_index) - str_index));
str_index += buf_line.size() + 1;
// Ignore control characters and greater/equal to 0x80, but preserve ESC (0x1B) if ANSI mode is enabled
buf_line.erase(std::remove_if(buf_line.begin(), buf_line.end(), [this](s8 c) {
buf_line.erase(std::remove_if(buf_line.begin(), buf_line.end(), [this](s8 c)
{
// If ANSI TTY is enabled, preserve ESC (0x1B) for ANSI sequences
if (m_ansi_tty)
{
// Keep ESC (0x1B) so ANSI sequences remain intact
return c <= 0x8 || c == 0x7F || (c >= 0xE && c <= 0x1F && c != 0x1B);
}
// Remove all control characters so output is clean
return c <= 0x8 || c == 0x7F || (c >= 0xE && c <= 0x1F);
}), buf_line.end());

View file

@ -1,11 +1,11 @@
#pragma once
#include "Utilities/File.h"
#include "rpcs3qt/syntax_highlighter.h"
#include "util/logs.hpp"
#include "custom_dock_widget.h"
#include "find_dialog.h"
#include "syntax_highlighter.h"
#include <memory>
@ -13,6 +13,8 @@
#include <QPlainTextEdit>
#include <QActionGroup>
class AnsiHighlighter;
class gui_settings;
class log_frame : public custom_dock_widget

View file

@ -34,7 +34,6 @@ private:
QString m_full_log;
QPlainTextEdit* m_log_text;
LogHighlighter* m_log_highlighter;
AnsiHighlighter* m_ansi_highlighter;
std::unique_ptr<find_dialog> m_find_dialog;
std::bitset<32> m_log_levels = std::bitset<32>(0b11111111u);
bool m_show_timestamps = true;

View file

@ -186,48 +186,49 @@ GlslHighlighter::GlslHighlighter(QTextDocument* parent) : Highlighter(parent)
AnsiHighlighter::AnsiHighlighter(QTextDocument* parent) : Highlighter(parent)
{
m_foreground_color = gui::utils::get_foreground_color();
}
void AnsiHighlighter::highlightBlock(const QString &text)
void AnsiHighlighter::highlightBlock(const QString& text)
{
// Match ANSI SGR sequences, e.g. "\x1b[31m" or "\x1b[1;32m"
const QRegularExpression ansi_re("\x1b\\[[0-9;]*m");
const QRegularExpression param_re("\x1b\\[([0-9;]*)m");
static const QRegularExpression ansi_re("\x1b\\[[0-9;]*m");
static const QRegularExpression param_re("\x1b\\[([0-9;]*)m");
QTextCharFormat escapeFormat;
escapeFormat.setForeground(Qt::darkGray);
escapeFormat.setFontItalic(true);
static QTextCharFormat escape_format;
escape_format.setForeground(Qt::darkGray);
escape_format.setFontItalic(true);
QTextCharFormat currentFormat;
currentFormat.setForeground(gui::utils::get_foreground_color());
static QTextCharFormat current_format;
current_format.setForeground(m_foreground_color);
int pos = 0;
auto it = ansi_re.globalMatch(text);
while (it.hasNext())
{
auto match = it.next();
int start = match.capturedStart();
int length = match.capturedLength();
const auto match = it.next();
const int start = match.capturedStart();
const int length = match.capturedLength();
// Apply current format to the chunk before this escape sequence
if (start > pos)
{
setFormat(pos, start - pos, currentFormat);
setFormat(pos, start - pos, current_format);
}
// Highlight the escape sequence itself
setFormat(start, length, escapeFormat);
setFormat(start, length, escape_format);
// Parse SGR parameters and update currentFormat
QRegularExpressionMatch pm = param_re.match(match.captured());
const QRegularExpressionMatch pm = param_re.match(match.captured());
if (pm.hasMatch())
{
QString params = pm.captured(1);
const QString params = pm.captured(1);
if (params.isEmpty())
{
// empty or just \x1b[m = reset
currentFormat = QTextCharFormat();
currentFormat.setForeground(gui::utils::get_foreground_color());
current_format = QTextCharFormat();
current_format.setForeground(m_foreground_color);
}
else
{
@ -240,35 +241,35 @@ void AnsiHighlighter::highlightBlock(const QString &text)
switch (code)
{
case 0:
currentFormat = QTextCharFormat();
currentFormat.setForeground(gui::utils::get_foreground_color());
current_format = QTextCharFormat();
current_format.setForeground(m_foreground_color);
break;
case 1:
currentFormat.setFontWeight(QFont::Bold);
current_format.setFontWeight(QFont::Bold);
break;
case 3:
currentFormat.setFontItalic(true);
current_format.setFontItalic(true);
break;
case 4:
currentFormat.setFontUnderline(true);
current_format.setFontUnderline(true);
break;
case 30: currentFormat.setForeground(Qt::black); break;
case 31: currentFormat.setForeground(Qt::red); break;
case 32: currentFormat.setForeground(Qt::darkGreen); break;
case 33: currentFormat.setForeground(Qt::darkYellow); break;
case 34: currentFormat.setForeground(Qt::darkBlue); break;
case 35: currentFormat.setForeground(Qt::darkMagenta); break;
case 36: currentFormat.setForeground(Qt::darkCyan); break;
case 37: currentFormat.setForeground(Qt::lightGray); break;
case 39: currentFormat.setForeground(gui::utils::get_foreground_color()); break;
case 90: currentFormat.setForeground(Qt::darkGray); break;
case 91: currentFormat.setForeground(Qt::red); break;
case 92: currentFormat.setForeground(Qt::green); break;
case 93: currentFormat.setForeground(Qt::yellow); break;
case 94: currentFormat.setForeground(Qt::blue); break;
case 95: currentFormat.setForeground(Qt::magenta); break;
case 96: currentFormat.setForeground(Qt::cyan); break;
case 97: currentFormat.setForeground(Qt::white); break;
case 30: current_format.setForeground(Qt::black); break;
case 31: current_format.setForeground(Qt::red); break;
case 32: current_format.setForeground(Qt::darkGreen); break;
case 33: current_format.setForeground(Qt::darkYellow); break;
case 34: current_format.setForeground(Qt::darkBlue); break;
case 35: current_format.setForeground(Qt::darkMagenta); break;
case 36: current_format.setForeground(Qt::darkCyan); break;
case 37: current_format.setForeground(Qt::lightGray); break;
case 39: current_format.setForeground(m_foreground_color); break;
case 90: current_format.setForeground(Qt::darkGray); break;
case 91: current_format.setForeground(Qt::red); break;
case 92: current_format.setForeground(Qt::green); break;
case 93: current_format.setForeground(Qt::yellow); break;
case 94: current_format.setForeground(Qt::blue); break;
case 95: current_format.setForeground(Qt::magenta); break;
case 96: current_format.setForeground(Qt::cyan); break;
case 97: current_format.setForeground(Qt::white); break;
// Background and extended colors not yet handled
default:
break;
@ -282,5 +283,5 @@ void AnsiHighlighter::highlightBlock(const QString &text)
// Apply remaining format
if (pos < text.length())
setFormat(pos, text.length() - pos, currentFormat);
setFormat(pos, text.length() - pos, current_format);
}

View file

@ -2,6 +2,7 @@
#include <QSyntaxHighlighter>
#include <QRegularExpression>
#include <qbrush.h>
// Inspired by https://doc.qt.io/qt-5/qtwidgets-richtext-syntaxhighlighter-example.html
@ -61,5 +62,7 @@ public:
explicit AnsiHighlighter(QTextDocument* parent = nullptr);
protected:
void highlightBlock(const QString &text) override;
QColor m_foreground_color;
void highlightBlock(const QString& text) override;
};