From 200557c036f3041626f63869a46c714d66b339e3 Mon Sep 17 00:00:00 2001 From: Megamouse Date: Thu, 19 Oct 2017 11:58:39 +0200 Subject: [PATCH] [Qt] Add syntax highlighter (#3550) * Qt: Add syntax highlighter * Qt: add syntax highlighter to cg_disasm The glsl part is pretty much finished. The asm is still missing a few rules. Colors are not yet fully decided * Qt: add filter for syntax highlighter --- rpcs3/rpcs3.vcxproj | 39 ++++++ rpcs3/rpcs3.vcxproj.filters | 27 ++++ rpcs3/rpcs3qt/cg_disasm_window.cpp | 114 +++++++++++++++++ rpcs3/rpcs3qt/game_list_frame.h | 4 +- rpcs3/rpcs3qt/syntax_highlighter.cpp | 183 +++++++++++++++++++++++++++ rpcs3/rpcs3qt/syntax_highlighter.h | 85 +++++++++++++ 6 files changed, 450 insertions(+), 2 deletions(-) create mode 100644 rpcs3/rpcs3qt/syntax_highlighter.cpp create mode 100644 rpcs3/rpcs3qt/syntax_highlighter.h diff --git a/rpcs3/rpcs3.vcxproj b/rpcs3/rpcs3.vcxproj index 6c861db438..5cec667b85 100644 --- a/rpcs3/rpcs3.vcxproj +++ b/rpcs3/rpcs3.vcxproj @@ -455,6 +455,11 @@ true true + + true + true + true + true true @@ -585,6 +590,11 @@ true true + + true + true + true + true true @@ -725,6 +735,11 @@ true true + + true + true + true + true true @@ -855,6 +870,11 @@ true true + + true + true + true + true true @@ -874,6 +894,7 @@ + @@ -1243,6 +1264,24 @@ + + $(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing syntax_highlighter.h... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_NO_DEBUG -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_QUICK_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -DLLVM_AVAILABLE -D_UNICODE "-I.\..\Vulkan\Vulkan-LoaderAndValidationLayers\include" "-I.\..\3rdparty\minidx12\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtQuick" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtQml" "-I$(QTDIR)\include\QtNetwork" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)\." "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" + $(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing syntax_highlighter.h... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_QUICK_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -D_SCL_SECURE_NO_WARNINGS -D_UNICODE "-I.\..\Vulkan\Vulkan-LoaderAndValidationLayers\include" "-I.\.." "-I.\..\3rdparty\minidx12\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtQuick" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtQml" "-I$(QTDIR)\include\QtNetwork" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)\." "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" + $(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing syntax_highlighter.h... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_NO_DEBUG -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_QUICK_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DNDEBUG -DQT_WINEXTRAS_LIB -D_UNICODE "-I.\..\Vulkan\Vulkan-LoaderAndValidationLayers\include" "-I.\..\3rdparty\minidx12\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtQuick" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtQml" "-I$(QTDIR)\include\QtNetwork" "-I$(QTDIR)\include\QtCore" "-I.\release" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)\." "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" + $(QTDIR)\bin\moc.exe;%(FullPath);$(QTDIR)\bin\moc.exe;%(FullPath) + Moc%27ing syntax_highlighter.h... + .\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp + "$(QTDIR)\bin\moc.exe" "%(FullPath)" -o ".\QTGeneratedFiles\$(ConfigurationName)\moc_%(Filename).cpp" -D_WINDOWS -DUNICODE -DWIN32 -DWIN64 -DQT_OPENGL_LIB -DQT_WIDGETS_LIB -DQT_QUICK_LIB -DQT_GUI_LIB -DQT_QML_LIB -DQT_NETWORK_LIB -DQT_CORE_LIB -DQT_WINEXTRAS_LIB -DLLVM_AVAILABLE -D_SCL_SECURE_NO_WARNINGS -D_UNICODE "-I.\..\Vulkan\Vulkan-LoaderAndValidationLayers\include" "-I.\.." "-I.\..\3rdparty\minidx12\Include" "-I$(QTDIR)\include" "-I$(QTDIR)\include\QtOpenGL" "-I$(QTDIR)\include\QtWidgets" "-I$(QTDIR)\include\QtQuick" "-I$(QTDIR)\include\QtGui" "-I$(QTDIR)\include\QtANGLE" "-I$(QTDIR)\include\QtQml" "-I$(QTDIR)\include\QtNetwork" "-I$(QTDIR)\include\QtCore" "-I.\debug" "-I$(QTDIR)\mkspecs\win32-msvc2015" "-I.\QTGeneratedFiles\$(ConfigurationName)\." "-I.\QTGeneratedFiles" "-I$(QTDIR)\include\QtWinExtras" + diff --git a/rpcs3/rpcs3.vcxproj.filters b/rpcs3/rpcs3.vcxproj.filters index c78ede2cde..226ac19247 100644 --- a/rpcs3/rpcs3.vcxproj.filters +++ b/rpcs3/rpcs3.vcxproj.filters @@ -90,6 +90,9 @@ {114eef25-61bf-4df5-8867-09156cf51b8a} + + {adb985ae-88db-4274-a6fd-6bcf230451ca} + @@ -488,6 +491,21 @@ Gui + + Generated Files\Release - LLVM + + + Generated Files\Debug + + + Generated Files\Release + + + Generated Files\Debug - LLVM + + + Gui\syntax highlighter + Gui\dev tools @@ -527,6 +545,9 @@ Gui\message dialog + + Generated Files\Debug + @@ -636,6 +657,9 @@ Gui\saves + + Gui\syntax highlighter + Gui\misc dialogs @@ -696,6 +720,9 @@ Gui\message dialog + + Generated Files + diff --git a/rpcs3/rpcs3qt/cg_disasm_window.cpp b/rpcs3/rpcs3qt/cg_disasm_window.cpp index 7d17489c71..24a4839d93 100644 --- a/rpcs3/rpcs3qt/cg_disasm_window.cpp +++ b/rpcs3/rpcs3qt/cg_disasm_window.cpp @@ -1,6 +1,7 @@ #include "stdafx.h" #include "cg_disasm_window.h" +#include "syntax_highlighter.h" #include #include @@ -39,6 +40,119 @@ cg_disasm_window::cg_disasm_window(std::shared_ptr xSettings): xgu m_glsl_text->setWordWrapMode(QTextOption::NoWrap); m_glsl_text->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + // m_disasm_text syntax highlighter + syntax_highlighter* sh_asm = new syntax_highlighter(m_disasm_text->document()); + sh_asm->AddCommentRule("#", QColor(Qt::darkGreen)); + sh_asm->AddCommentRule("\/\*", QColor(Qt::darkGreen), true, "\*\/"); + sh_asm->AddSimpleRule(QStringList("^([^\\s]+).*$"), QColor(Qt::darkBlue)); // Instructions + sh_asm->AddSimpleRule(QStringList(",?\\s(-?R\\d[^,;\\s]*)"), QColor(Qt::darkRed)); // -R0.* + sh_asm->AddSimpleRule(QStringList(",?\\s(-?H\\d[^,;\\s]*)"), QColor(Qt::red)); // -H1.* + sh_asm->AddSimpleRule(QStringList(",?\\s(-?v\\[\\d\\]*[^,;\\s]*)"), QColor(Qt::darkCyan)); // -v[xyz].* + sh_asm->AddSimpleRule(QStringList(",?\\s(-?o\\[\\d\\]*[^,;\\s]*)"), QColor(Qt::darkMagenta)); // -o[xyz].* + sh_asm->AddSimpleRule(QStringList(",?\\s(-?c\\[\\d\\]*[^,;\\s]*)"), QColor(Qt::darkYellow)); // -c[xyz].* + //sh_asm->AddMultiRule( + // "^([^\\s]+)(?:,?\\s*([^,\\;\\s]+))?(?:,?\\s*([^,\\;\\s]+))?(?:,?\\s*([^,\\;\\s]+))?(?:,?\\s*([^,\\;\\s]+))?.*$", + // { QColor(Qt::black), QColor(Qt::darkBlue), QColor(Qt::darkRed), QColor(Qt::darkMagenta), QColor(Qt::darkYellow), QColor(Qt::darkCyan) }); + + // m_glsl_text syntax highlighter + QStringList glsl_syntax = QStringList() + // Selection-Iteration-Jump Statements: + << "if" << "else" << "switch" << "case" << "default" + << "for" << "while" << "do" << "foreach" //? + << "return" << "break" << "continue" << "discard" + // Misc: + << "void" << "char" << "short" << "long" << "template" + << "class" << "struct" << "union" << "enum" + << "static" << "virtual" << "inline" << "explicit" + << "public" << "private" << "protected" << "namespace" + << "typedef" << "typename" << "signed" << "unsigned" + << "friend" << "operator" << "signals" << "slots" + // Qualifiers: + << "in" << "packed" << "precision" << "const" << "smooth" << "sample" + << "out" << "shared" << "highp" << "invariant" << "noperspective" << "centroid" + << "inout" << "std140" << "mediump" << "uniform" << "flat" << "patch" + << "buffer" << "std430" << "lowp" + << "image" + // Removed Qualifiers? + << "attribute" << "varying" + // Memory Qualifiers + << "coherent" << "volatile" << "restrict" << "readonly" << "writeonly" + // Layout Qualifiers: + //<< "subroutine" << "layout" << "xfb_buffer" << "textureGrad" << "texture" << "dFdx" << "dFdy" + //<< "location" << "component" << "binding" << "offset" + //<< "xfb_offset" << "xfb_stride" << "vertices" << "max_vertices" + // Scalars and Vectors: + << "bool" << "int" << "uint" << "float" << "double" + << "bvec2" << "ivec2" << "uvec2" << "vec2" << "dvec2" + << "bvec3" << "ivec3" << "uvec3" << "vec3" << "dvec3" + << "bvec4" << "ivec4" << "uvec4" << "vec4" << "dvec4" + // Matrices: + << "mat2" << "mat2x3" << "mat2x4" + << "mat3" << "mat3x2" << "mat3x4" + << "mat4" << "mat4x2" << "mat4x3" + // Sampler Types: + << "sampler1D" << "isampler1D" << "usampler1D" + << "sampler2D" << "isampler2D" << "usampler2D" + << "sampler3D" << "isampler3D" << "usampler3D" + << "samplerCube" << "isamplerCube" << "usamplerCube" + << "sampler2DRect" << "isampler2DRect" << "usampler2DRect" + << "sampler1DArray" << "isampler1DArray" << "usampler1DArray" + << "sampler2DArray" << "isampler2DArray" << "usampler2DArray" + << "samplerCubeArray" << "isamplerCubeArray" << "usamplerCubeArray" + << "samplerBuffer" << "isamplerBuffer" << "usamplerBuffer" + << "sampler2DMS" << "isampler2DMS" << "usampler2DMS" + << "sampler2DMSArray" << "isampler2DMSArray" << "usampler2DMSArray" + // Shadow Samplers: + << "sampler1DShadow" + << "sampler2DShadow" + << "samplerCubeShadow" + << "sampler2DRectShadow" + << "sampler1DArrayShadow" + << "sampler2DArrayShadow" + << "samplerCubeArrayShadow" + // Image Types: + << "image1D" << "iimage1D" << "uimage1D" + << "image2D" << "iimage2D" << "uimage2D" + << "image3D" << "iimage3D" << "uimage3D" + << "imageCube" << "iimageCube" << "uimageCube" + << "image2DRect" << "iimage2DRect" << "uimage2DRect" + << "image1DArray" << "iimage1DArray" << "uimage1DArray" + << "image2DArray" << "iimage2DArray" << "uimage2DArray" + << "imageCubeArray" << "iimageCubeArray" << "uimageCubeArray" + << "imageBuffer" << "iimageBuffer" << "uimageBuffer" + << "image2DMS" << "iimage2DMS" << "uimage2DMS" + << "image2DMSArray" << "iimage2DMSArray" << "uimage2DMSArray" + // Image Formats: + // Floating-point: // Signed integer: + << "rgba32f" << "rgba32i" + << "rgba16f" << "rgba16i" + << "rg32f" << "rgba8i" + << "rg16f" << "rg32i" + << "r11f_g11f_b10f" << "rg16i" + << "r32f" << "rg8i" + << "r16f" << "r32i" + << "rgba16" << "r16i" + << "rgb10_a2" << "r8i" + << "rgba8" << "r8ui" + << "rg16" // Unsigned integer: + << "rg8" << "rgba32ui" + << "r16" << "rgba16ui" + << "r8" << "rgb10_a2ui" + << "rgba16_snorm" << "rgba8ui" + << "rgba8_snorm" << "rg32ui" + << "rg16_snorm" << "rg16ui" + << "rg8_snorm" << "rg8ui" + << "r16_snorm" << "r32ui" + << "r8_snorm" << "r16ui" + ; + syntax_highlighter* sh_glsl = new syntax_highlighter(m_glsl_text->document()); + sh_glsl->AddWordRule(glsl_syntax, QColor(Qt::darkBlue)); // normal words like: soka, nani, or gomen + sh_glsl->AddSimpleRule(QStringList("\\bGL_(?:[A-Z]|_)+\\b"), QColor(Qt::darkMagenta)); // constants like: GL_OMAE_WA_MOU_SHINDEIRU + sh_glsl->AddSimpleRule(QStringList("\\bgl_(?:[A-Z]|[a-z]|_)+\\b"), QColor(Qt::darkCyan)); // reserved types like: gl_exploooooosion + sh_glsl->AddSimpleRule(QStringList("\\B#[^\\s]+\\b"), QColor(Qt::darkGray)); // preprocessor instructions like: #waifu megumin + sh_glsl->AddCommentRule("\/\/", QColor(Qt::darkGreen)); // comments like: // No comment + sh_glsl->AddCommentRule("\/\*", QColor(Qt::darkGreen), true, "\*\/"); // comments like: /* I am trapped! Please help me! */ + QSplitter* splitter = new QSplitter(); splitter->addWidget(m_disasm_text); splitter->addWidget(m_glsl_text); diff --git a/rpcs3/rpcs3qt/game_list_frame.h b/rpcs3/rpcs3qt/game_list_frame.h index 7f42022fde..069b018333 100644 --- a/rpcs3/rpcs3qt/game_list_frame.h +++ b/rpcs3/rpcs3qt/game_list_frame.h @@ -103,9 +103,9 @@ namespace category // (see PARAM.SFO in psdevwiki.com) TODO: Disc Categories inline bool CategoryInMap(const std::string& cat, const q_from_char& map) { - auto map_contains_category = [cat](std::pair p) + auto map_contains_category = [s = qstr(cat)](const auto& p) { - return p.second == qstr(cat); + return p.second == s; }; return std::find_if(map.begin(), map.end(), map_contains_category) != map.end(); diff --git a/rpcs3/rpcs3qt/syntax_highlighter.cpp b/rpcs3/rpcs3qt/syntax_highlighter.cpp new file mode 100644 index 0000000000..2db72e0957 --- /dev/null +++ b/rpcs3/rpcs3qt/syntax_highlighter.cpp @@ -0,0 +1,183 @@ +#include "syntax_highlighter.h" + +syntax_highlighter::syntax_highlighter(QTextDocument *parent) : QSyntaxHighlighter(parent) +{ +} + +void syntax_highlighter::AddSimpleRule(SimpleRule rule) +{ + rule.expressions.erase(std::remove_if(rule.expressions.begin(), rule.expressions.end(), [&](const auto &e){ + return IsInvalidExpression(e) || e.captureCount() > 1; + }), rule.expressions.end()); + + if (rule.expressions.isEmpty()) + { + return; + } + m_rules.append(rule); +} + +void syntax_highlighter::AddSimpleRule(const QStringList& expressions, const QColor& color) +{ + QTextCharFormat format; + format.setForeground(color); + QVector regexs; + for (const auto& expr : expressions) + { + regexs.append(QRegularExpression(expr)); + } + AddSimpleRule(SimpleRule(regexs, format)); +} + +void syntax_highlighter::AddWordRule(const QStringList& words, const QColor& color) +{ + QTextCharFormat format; + format.setForeground(color); + QVector regexs; + for (const auto& word : words) + { + regexs.append(QRegularExpression("\\b" + word + "\\b")); + } + AddSimpleRule(SimpleRule(regexs, format)); +} + +void syntax_highlighter::AddMultiRule(const MultiRule& rule) +{ + if (IsInvalidExpression(rule.expression) || rule.formats.length() <= rule.expression.captureCount()) + { + return; + } + + m_multi_rules.append(rule); +} +void syntax_highlighter::AddMultiRule(const QString& expression, const QVector& colors) +{ + QVector formats; + for (const auto& color : colors) + { + QTextCharFormat format; + format.setForeground(color); + formats.append(format); + } + AddMultiRule(MultiRule(QRegularExpression(expression), formats)); +} + +void syntax_highlighter::AddCommentRule(const CommentRule& rule) +{ + if (IsInvalidExpression(rule.start_expression) || (rule.multi_line && IsInvalidExpression(rule.end_expression))) + { + return; + } + m_comment_rules.append(rule); +} + +void syntax_highlighter::AddCommentRule(const QString& start, const QColor& color, bool multi_line, const QString& end) +{ + QTextCharFormat format; + format.setForeground(color); + AddCommentRule(CommentRule(QRegularExpression(start), QRegularExpression(end), format, multi_line)); +} + +void syntax_highlighter::highlightBlock(const QString &text) +{ + m_current_block = text; + + for (const auto& rule : m_multi_rules) + { + // Search for all the matching strings + QRegularExpressionMatchIterator iter = rule.expression.globalMatch(m_current_block); + + // Iterate through the matching strings + while (iter.hasNext()) + { + // Apply formats to their respective found groups + QRegularExpressionMatch match = iter.next(); + + for (int i = 0; i <= match.lastCapturedIndex(); i++) + { + setFormat(match.capturedStart(i), match.capturedLength(i), rule.formats[i]); + } + } + } + + for (const auto& rule : m_rules) + { + for (const auto& expression : rule.expressions) + { + // Search for all the matching strings + QRegularExpressionMatchIterator iter = expression.globalMatch(m_current_block); + bool contains_group = expression.captureCount() > 0; + + // Iterate through the matching strings + while (iter.hasNext()) + { + // Apply format to the matching string + QRegularExpressionMatch match = iter.next(); + if (contains_group) + { + setFormat(match.capturedStart(1), match.capturedLength(1), rule.format); + } + else + { + setFormat(match.capturedStart(), match.capturedLength(), rule.format); + } + } + } + } + + for (const auto& rule : m_comment_rules) + { + int comment_start = 0; // Current comment's start position in the text block + int comment_end = 0; // Current comment's end position in the text block + int comment_length = 0; // Current comment length + + // We assume we end outside a comment until we know better + setCurrentBlockState(ENDED_OUTSIDE_COMMENT); + + // Search for the first comment in this block if we start outside or don't want to search for multiline comments + if (!rule.multi_line || previousBlockState() != ENDED_INSIDE_COMMENT) + { + comment_start = m_current_block.indexOf(rule.start_expression); + } + + // Set format for the rest of this block/line + if (!rule.multi_line) + { + comment_length = m_current_block.length() - comment_start; + setFormat(comment_start, comment_length, rule.format); + break; + } + + // Format all comments in this block (if they exist) + while (comment_start >= 0) + { + // Search for end of comment in the remaining text + QRegularExpressionMatch match = rule.end_expression.match(m_current_block, comment_start); + comment_end = match.capturedStart(); + match.captured(1); + + if (comment_end == -1) + { + // We end inside a comment and want to format the entire remaining text + setCurrentBlockState(ENDED_INSIDE_COMMENT); + comment_length = m_current_block.length() - comment_start; + } + else + { + // We found the end of the comment so we need to go another round + comment_length = comment_end - comment_start + match.capturedLength(); + } + + // Set format for this text segment + setFormat(comment_start, comment_length, rule.format); + + // Search for the next comment + comment_start = m_current_block.indexOf(rule.start_expression, comment_start + comment_length); + } + } +} + +bool syntax_highlighter::IsInvalidExpression(const QRegularExpression& expression) +{ + return !expression.isValid() || expression.pattern().isEmpty(); +} diff --git a/rpcs3/rpcs3qt/syntax_highlighter.h b/rpcs3/rpcs3qt/syntax_highlighter.h new file mode 100644 index 0000000000..0ff076c06c --- /dev/null +++ b/rpcs3/rpcs3qt/syntax_highlighter.h @@ -0,0 +1,85 @@ +#pragma once + +#include +#include + +class syntax_highlighter : public QSyntaxHighlighter +{ + Q_OBJECT + + enum BlockState + { + ENDED_OUTSIDE_COMMENT, + ENDED_INSIDE_COMMENT + }; + +public: + syntax_highlighter(QTextDocument *parent = 0); + + struct SimpleRule + { + QVector expressions; + QTextCharFormat format; + SimpleRule(){} + SimpleRule(const QVector& expressions, const QTextCharFormat& format) + : expressions(expressions), format(format) {} + }; + struct MultiRule + { + QRegularExpression expression; + QVector formats; + MultiRule(){} + MultiRule(const QRegularExpression& expr, const QVector& formats) + : expression(expr), formats(formats) {} + }; + struct CommentRule + { + QRegularExpression start_expression; + QRegularExpression end_expression; + QTextCharFormat format; + bool multi_line = false; + CommentRule(){} + CommentRule(const QRegularExpression& start, const QRegularExpression& end, const QTextCharFormat& format, bool multi_line) + : start_expression(start), end_expression(end), format(format), multi_line(multi_line) {} + }; + + /** + Add a simple highlighting rule that applies the given format to all given expressions. + You can add up to one Group to the expression. The full match group will be ignored + */ + void AddSimpleRule(SimpleRule rule); + void AddSimpleRule(const QStringList& expressions, const QColor& color); + /** Add a simple highlighting rule for words. Not supposed to be used with any other expression */ + void AddWordRule(const QStringList& words, const QColor& color); + + /** + Add a complex highlighting rule that applies different formats to the expression's groups. + Make sure you don't have more groups in your expression than formats !!! + Group 0 is always the full match, so the first color has to be for that !!! + Example expression for string "rdch $4,$6,$8,$5" with 6 groups (5 + full match): + "^(?[^\s]+) (?[^,]+),(?[^,]+),(?[^,]+),(?[^,]+)$" + */ + void AddMultiRule(const MultiRule& rule); + void AddMultiRule(const QString& expression, const QVector& colors); + + /** + Add a comment highlighting rule. Add them in ascending priority. + We only need rule.end_expression in case of rule.multi_line. + A block ends at the end of a line anyway. + */ + void AddCommentRule(const CommentRule& rule); + void AddCommentRule(const QString& start, const QColor& color, bool multi_line = false, const QString& end = ""); + +protected: + void highlightBlock(const QString &text) override; + +private: + /** Checks if an expression is invalid or empty */ + static bool IsInvalidExpression(const QRegularExpression& expression); + + QVector m_rules; + QVector m_comment_rules; + QVector m_multi_rules; + + QString m_current_block; +};