UI: colored ANSI logs
This commit is contained in:
@@ -3,6 +3,7 @@
|
|||||||
#include "gui_settings.h"
|
#include "gui_settings.h"
|
||||||
#include "hex_validator.h"
|
#include "hex_validator.h"
|
||||||
#include "memory_viewer_panel.h"
|
#include "memory_viewer_panel.h"
|
||||||
|
#include "syntax_highlighter.h"
|
||||||
|
|
||||||
#include "Utilities/lockless.h"
|
#include "Utilities/lockless.h"
|
||||||
#include "util/asm.hpp"
|
#include "util/asm.hpp"
|
||||||
@@ -131,6 +132,8 @@ log_frame::log_frame(std::shared_ptr<gui_settings> _gui_settings, QWidget* paren
|
|||||||
m_tty->setContextMenuPolicy(Qt::CustomContextMenu);
|
m_tty->setContextMenuPolicy(Qt::CustomContextMenu);
|
||||||
m_tty->document()->setMaximumBlockCount(max_block_count_tty);
|
m_tty->document()->setMaximumBlockCount(max_block_count_tty);
|
||||||
m_tty->installEventFilter(this);
|
m_tty->installEventFilter(this);
|
||||||
|
|
||||||
|
m_tty_ansi_highlighter = new AnsiHighlighter(m_tty->document());
|
||||||
|
|
||||||
m_tty_input = new QLineEdit();
|
m_tty_input = new QLineEdit();
|
||||||
if (m_tty_channel >= 0)
|
if (m_tty_channel >= 0)
|
||||||
@@ -599,8 +602,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));
|
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;
|
str_index += buf_line.size() + 1;
|
||||||
|
|
||||||
// Ignore control characters and greater/equal to 0x80
|
// 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(), [](s8 c) { return c <= 0x8 || c == 0x7F || (c >= 0xE && c <= 0x1F); }), buf_line.end());
|
buf_line.erase(std::remove_if(buf_line.begin(), buf_line.end(), [this](s8 c) {
|
||||||
|
if (m_ansi_tty)
|
||||||
|
{
|
||||||
|
// Keep ESC (0x1B) so ANSI sequences remain intact
|
||||||
|
return c <= 0x8 || c == 0x7F || (c >= 0xE && c <= 0x1F && c != 0x1B);
|
||||||
|
}
|
||||||
|
return c <= 0x8 || c == 0x7F || (c >= 0xE && c <= 0x1F);
|
||||||
|
}), buf_line.end());
|
||||||
|
|
||||||
// save old scroll bar state
|
// save old scroll bar state
|
||||||
QScrollBar* sb = m_tty->verticalScrollBar();
|
QScrollBar* sb = m_tty->verticalScrollBar();
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
|
|
||||||
#include "custom_dock_widget.h"
|
#include "custom_dock_widget.h"
|
||||||
#include "find_dialog.h"
|
#include "find_dialog.h"
|
||||||
|
#include "syntax_highlighter.h"
|
||||||
|
|
||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
@@ -69,6 +70,7 @@ private:
|
|||||||
QPlainTextEdit* m_tty = nullptr;
|
QPlainTextEdit* m_tty = nullptr;
|
||||||
QLineEdit* m_tty_input = nullptr;
|
QLineEdit* m_tty_input = nullptr;
|
||||||
int m_tty_channel = -1;
|
int m_tty_channel = -1;
|
||||||
|
AnsiHighlighter* m_tty_ansi_highlighter = nullptr;
|
||||||
|
|
||||||
QAction* m_clear_act = nullptr;
|
QAction* m_clear_act = nullptr;
|
||||||
QAction* m_clear_tty_act = nullptr;
|
QAction* m_clear_tty_act = nullptr;
|
||||||
|
|||||||
@@ -9,6 +9,7 @@
|
|||||||
#include <memory>
|
#include <memory>
|
||||||
|
|
||||||
class LogHighlighter;
|
class LogHighlighter;
|
||||||
|
class AnsiHighlighter;
|
||||||
class gui_settings;
|
class gui_settings;
|
||||||
|
|
||||||
class log_viewer : public QWidget
|
class log_viewer : public QWidget
|
||||||
@@ -33,6 +34,7 @@ private:
|
|||||||
QString m_full_log;
|
QString m_full_log;
|
||||||
QPlainTextEdit* m_log_text;
|
QPlainTextEdit* m_log_text;
|
||||||
LogHighlighter* m_log_highlighter;
|
LogHighlighter* m_log_highlighter;
|
||||||
|
AnsiHighlighter* m_ansi_highlighter;
|
||||||
std::unique_ptr<find_dialog> m_find_dialog;
|
std::unique_ptr<find_dialog> m_find_dialog;
|
||||||
std::bitset<32> m_log_levels = std::bitset<32>(0b11111111u);
|
std::bitset<32> m_log_levels = std::bitset<32>(0b11111111u);
|
||||||
bool m_show_timestamps = true;
|
bool m_show_timestamps = true;
|
||||||
|
|||||||
@@ -183,3 +183,104 @@ GlslHighlighter::GlslHighlighter(QTextDocument* parent) : Highlighter(parent)
|
|||||||
commentStartExpression = QRegularExpression("/\\*");
|
commentStartExpression = QRegularExpression("/\\*");
|
||||||
commentEndExpression = QRegularExpression("\\*/");
|
commentEndExpression = QRegularExpression("\\*/");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
AnsiHighlighter::AnsiHighlighter(QTextDocument* parent) : Highlighter(parent)
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
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");
|
||||||
|
|
||||||
|
QTextCharFormat escapeFormat;
|
||||||
|
escapeFormat.setForeground(Qt::darkGray);
|
||||||
|
escapeFormat.setFontItalic(true);
|
||||||
|
|
||||||
|
QTextCharFormat currentFormat;
|
||||||
|
currentFormat.setForeground(gui::utils::get_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();
|
||||||
|
|
||||||
|
// Apply current format to the chunk before this escape sequence
|
||||||
|
if (start > pos)
|
||||||
|
{
|
||||||
|
setFormat(pos, start - pos, currentFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Highlight the escape sequence itself
|
||||||
|
setFormat(start, length, escapeFormat);
|
||||||
|
|
||||||
|
// Parse SGR parameters and update currentFormat
|
||||||
|
QRegularExpressionMatch pm = param_re.match(match.captured());
|
||||||
|
if (pm.hasMatch())
|
||||||
|
{
|
||||||
|
QString params = pm.captured(1);
|
||||||
|
if (params.isEmpty())
|
||||||
|
{
|
||||||
|
// empty or just \x1b[m = reset
|
||||||
|
currentFormat = QTextCharFormat();
|
||||||
|
currentFormat.setForeground(gui::utils::get_foreground_color());
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
const QStringList codes = params.split(';', Qt::SkipEmptyParts);
|
||||||
|
for (const QString& c : codes)
|
||||||
|
{
|
||||||
|
bool ok = false;
|
||||||
|
const int code = c.toInt(&ok);
|
||||||
|
if (!ok) continue;
|
||||||
|
switch (code)
|
||||||
|
{
|
||||||
|
case 0:
|
||||||
|
currentFormat = QTextCharFormat();
|
||||||
|
currentFormat.setForeground(gui::utils::get_foreground_color());
|
||||||
|
break;
|
||||||
|
case 1:
|
||||||
|
currentFormat.setFontWeight(QFont::Bold);
|
||||||
|
break;
|
||||||
|
case 3:
|
||||||
|
currentFormat.setFontItalic(true);
|
||||||
|
break;
|
||||||
|
case 4:
|
||||||
|
currentFormat.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;
|
||||||
|
// Background and extended colors not yet handled
|
||||||
|
default:
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pos = start + length;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Apply remaining format
|
||||||
|
if (pos < text.length())
|
||||||
|
setFormat(pos, text.length() - pos, currentFormat);
|
||||||
|
}
|
||||||
|
|||||||
@@ -52,3 +52,14 @@ class GlslHighlighter : public Highlighter
|
|||||||
public:
|
public:
|
||||||
explicit GlslHighlighter(QTextDocument* parent = nullptr);
|
explicit GlslHighlighter(QTextDocument* parent = nullptr);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class AnsiHighlighter : public Highlighter
|
||||||
|
{
|
||||||
|
Q_OBJECT
|
||||||
|
|
||||||
|
public:
|
||||||
|
explicit AnsiHighlighter(QTextDocument* parent = nullptr);
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void highlightBlock(const QString &text) override;
|
||||||
|
};
|
||||||
|
|||||||
Reference in New Issue
Block a user