#include "SyntaxHighlighter.h" #include // =========================================================================== // SyntaxHighlighter – Basis // =========================================================================== SyntaxHighlighter::SyntaxHighlighter(QTextDocument *parent) : QSyntaxHighlighter(parent) { } void SyntaxHighlighter::highlightBlock(const QString &text) { // Einzel-Zeilen-Regeln anwenden for (const HighlightRule &rule : m_rules) { QRegularExpressionMatchIterator it = rule.pattern.globalMatch(text); while (it.hasNext()) { QRegularExpressionMatch match = it.next(); setFormat( static_cast(match.capturedStart()), static_cast(match.capturedLength()), rule.format ); } } if (!m_hasMultiLineComment) { setCurrentBlockState(0); return; } // Mehrzeilige Kommentare // Zustand 0 = normal, 1 = mitten in /* ... */ setCurrentBlockState(0); // Leere Zeile mitten im Kommentar — Zustand weitertragen if (text.isEmpty()) { if (previousBlockState() == 1) { setCurrentBlockState(1); } return; } int startIndex = 0; if (previousBlockState() == 1) { // Vorherige Zeile war mitten im Kommentar — ab Zeilenanfang nach Ende suchen QRegularExpressionMatch endMatch = m_commentEndExpression.match(text, 0); if (endMatch.hasMatch()) { // Ende des Kommentars gefunden const int commentLength = static_cast(endMatch.capturedStart()) + static_cast(endMatch.capturedLength()); setFormat(0, commentLength, m_multiLineCommentFormat); // Nach weiteren Kommentaren in der gleichen Zeile suchen QRegularExpressionMatch nextStart = m_commentStartExpression.match(text, commentLength); startIndex = nextStart.hasMatch() ? static_cast(nextStart.capturedStart()) : -1; } else { // Noch kein Ende — gesamte Zeile ist Kommentar setFormat(0, text.length(), m_multiLineCommentFormat); setCurrentBlockState(1); return; } } else { // Neuen Kommentaranfang suchen QRegularExpressionMatch m = m_commentStartExpression.match(text); startIndex = m.hasMatch() ? static_cast(m.capturedStart()) : -1; } while (startIndex >= 0) { QRegularExpressionMatch endMatch = m_commentEndExpression.match(text, startIndex); int commentLength = 0; if (endMatch.hasMatch()) { commentLength = static_cast(endMatch.capturedStart()) - startIndex + static_cast(endMatch.capturedLength()); } else { // Kommentar geht über Zeilenende setCurrentBlockState(1); commentLength = text.length() - startIndex; } setFormat(startIndex, commentLength, m_multiLineCommentFormat); if (!endMatch.hasMatch()) { break; } // Nach weiteren Kommentaren in dieser Zeile suchen QRegularExpressionMatch nextStart = m_commentStartExpression.match(text, startIndex + commentLength); startIndex = nextStart.hasMatch() ? static_cast(nextStart.capturedStart()) : -1; } } // =========================================================================== // CppHighlighter // =========================================================================== CppHighlighter::CppHighlighter(QTextDocument *parent) : SyntaxHighlighter(parent) { m_hasMultiLineComment = true; QTextCharFormat keywordFormat; keywordFormat.setForeground(QColor("#569CD6")); keywordFormat.setFontWeight(QFont::Bold); const QStringList keywords = { "alignas","alignof","and","and_eq","asm","auto","bitand","bitor", "bool","break","case","catch","char","char8_t","char16_t","char32_t", "class","compl","concept","const","consteval","constexpr","constinit", "const_cast","continue","co_await","co_return","co_yield","decltype", "default","delete","do","double","dynamic_cast","else","enum", "explicit","export","extern","false","float","for","friend","goto", "if","inline","int","long","mutable","namespace","new","noexcept", "not","not_eq","nullptr","operator","or","or_eq","private","protected", "public","register","reinterpret_cast","requires","return","short", "signed","sizeof","static","static_assert","static_cast","struct", "switch","template","this","thread_local","throw","true","try", "typedef","typeid","typename","union","unsigned","using","virtual", "void","volatile","wchar_t","while","xor","xor_eq","override","final" }; for (const QString &kw : keywords) { HighlightRule rule; rule.pattern = QRegularExpression(QString("\\b%1\\b").arg(kw)); rule.format = keywordFormat; m_rules.append(rule); } QTextCharFormat preprocFormat; preprocFormat.setForeground(QColor("#C586C0")); { HighlightRule r; r.pattern = QRegularExpression("^\\s*#\\s*\\w+"); r.format = preprocFormat; m_rules.append(r); } QTextCharFormat stringFormat; stringFormat.setForeground(QColor("#CE9178")); { HighlightRule r; r.pattern = QRegularExpression(R"("(?:[^"\\]|\\.)*")"); r.format = stringFormat; m_rules.append(r); } { HighlightRule r; r.pattern = QRegularExpression(R"('(?:[^'\\]|\\.)*')"); r.format = stringFormat; m_rules.append(r); } QTextCharFormat numberFormat; numberFormat.setForeground(QColor("#B5CEA8")); { HighlightRule r; r.pattern = QRegularExpression(R"(\b(0[xX][0-9A-Fa-f]+[uUlL]*|[0-9]+\.?[0-9]*([eE][+-]?[0-9]+)?[fFlLuU]*)\b)"); r.format = numberFormat; m_rules.append(r); } QTextCharFormat commentFormat; commentFormat.setForeground(QColor("#6A9955")); commentFormat.setFontItalic(true); { HighlightRule r; r.pattern = QRegularExpression("//[^\n]*"); r.format = commentFormat; m_rules.append(r); } m_multiLineCommentFormat = commentFormat; m_commentStartExpression = QRegularExpression(R"(/\*)"); m_commentEndExpression = QRegularExpression(R"(\*/)"); } void CppHighlighter::highlightBlock(const QString &text) { SyntaxHighlighter::highlightBlock(text); } // =========================================================================== // CssHighlighter // =========================================================================== CssHighlighter::CssHighlighter(QTextDocument *parent) : SyntaxHighlighter(parent) { m_hasMultiLineComment = true; // Selektoren: .klasse #id element ::pseudo :pseudo QTextCharFormat selectorFormat; selectorFormat.setForeground(QColor("#D7BA7D")); { HighlightRule r; r.pattern = QRegularExpression(R"([.#]?[\w-]+\s*(?=\s*[,{]))"); r.format = selectorFormat; m_rules.append(r); } { HighlightRule r; r.pattern = QRegularExpression(R"(:{1,2}[\w-]+)"); r.format = selectorFormat; m_rules.append(r); } // Eigenschaften (property:) QTextCharFormat propFormat; propFormat.setForeground(QColor("#9CDCFE")); { HighlightRule r; r.pattern = QRegularExpression(R"([\w-]+\s*(?=:))"); r.format = propFormat; m_rules.append(r); } // Werte – Farben #hex QTextCharFormat colorFormat; colorFormat.setForeground(QColor("#CE9178")); { HighlightRule r; r.pattern = QRegularExpression(R"(#[0-9A-Fa-f]{3,8}\b)"); r.format = colorFormat; m_rules.append(r); } // Zahlen + Einheiten QTextCharFormat numberFormat; numberFormat.setForeground(QColor("#B5CEA8")); { HighlightRule r; r.pattern = QRegularExpression(R"(\b\d+\.?\d*(px|em|rem|%|vh|vw|pt|cm|mm|s|ms)?\b)"); r.format = numberFormat; m_rules.append(r); } // Strings QTextCharFormat stringFormat; stringFormat.setForeground(QColor("#CE9178")); { HighlightRule r; r.pattern = QRegularExpression(R"("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')"); r.format = stringFormat; m_rules.append(r); } // !important QTextCharFormat importantFormat; importantFormat.setForeground(QColor("#F44747")); importantFormat.setFontWeight(QFont::Bold); { HighlightRule r; r.pattern = QRegularExpression(R"(!important)"); r.format = importantFormat; m_rules.append(r); } // @-Regeln QTextCharFormat atFormat; atFormat.setForeground(QColor("#C586C0")); { HighlightRule r; r.pattern = QRegularExpression(R"(@[\w-]+)"); r.format = atFormat; m_rules.append(r); } QTextCharFormat commentFormat; commentFormat.setForeground(QColor("#6A9955")); commentFormat.setFontItalic(true); m_multiLineCommentFormat = commentFormat; m_commentStartExpression = QRegularExpression(R"(/\*)"); m_commentEndExpression = QRegularExpression(R"(\*/)"); } void CssHighlighter::highlightBlock(const QString &text) { SyntaxHighlighter::highlightBlock(text); } // =========================================================================== // HtmlHighlighter // =========================================================================== HtmlHighlighter::HtmlHighlighter(QTextDocument *parent) : SyntaxHighlighter(parent) { // Tag-Namen
QTextCharFormat tagFormat; tagFormat.setForeground(QColor("#569CD6")); { HighlightRule r; r.pattern = QRegularExpression(R"()"); r.format = tagFormat; m_rules.append(r); } // Attribute name= QTextCharFormat attrFormat; attrFormat.setForeground(QColor("#9CDCFE")); { HighlightRule r; r.pattern = QRegularExpression(R"(\b[\w:-]+=)"); r.format = attrFormat; m_rules.append(r); } // Attributwerte "wert" 'wert' QTextCharFormat valueFormat; valueFormat.setForeground(QColor("#CE9178")); { HighlightRule r; r.pattern = QRegularExpression(R"("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')"); r.format = valueFormat; m_rules.append(r); } // DOCTYPE QTextCharFormat doctypeFormat; doctypeFormat.setForeground(QColor("#808080")); { HighlightRule r; r.pattern = QRegularExpression(R"(]*>)", QRegularExpression::CaseInsensitiveOption); r.format = doctypeFormat; m_rules.append(r); } // Entities & { QTextCharFormat entityFormat; entityFormat.setForeground(QColor("#D7BA7D")); { HighlightRule r; r.pattern = QRegularExpression(R"(&(?:#\d+|#x[0-9A-Fa-f]+|[\w]+);)"); r.format = entityFormat; m_rules.append(r); } // Kommentare (mehrzeilig) QTextCharFormat commentFormat; commentFormat.setForeground(QColor("#6A9955")); commentFormat.setFontItalic(true); m_multiLineCommentFormat = commentFormat; m_commentStartExpression = QRegularExpression(""); m_hasMultiLineComment = true; } void HtmlHighlighter::highlightBlock(const QString &text) { SyntaxHighlighter::highlightBlock(text); } // =========================================================================== // PhpHighlighter // =========================================================================== PhpHighlighter::PhpHighlighter(QTextDocument *parent) : SyntaxHighlighter(parent) { // ---- HTML-Regeln (Basis, für den Teil außerhalb von ) ---- QTextCharFormat tagFormat; tagFormat.setForeground(QColor("#569CD6")); { HighlightRule r; r.pattern = QRegularExpression(R"()"); r.format = tagFormat; m_rules.append(r); } QTextCharFormat attrFormat; attrFormat.setForeground(QColor("#9CDCFE")); { HighlightRule r; r.pattern = QRegularExpression(R"(\b[\w:-]+=)"); r.format = attrFormat; m_rules.append(r); } QTextCharFormat valueFormat; valueFormat.setForeground(QColor("#CE9178")); { HighlightRule r; r.pattern = QRegularExpression(R"("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')"); r.format = valueFormat; m_rules.append(r); } QTextCharFormat entityFormat; entityFormat.setForeground(QColor("#D7BA7D")); { HighlightRule r; r.pattern = QRegularExpression(R"(&(?:#\d+|#x[0-9A-Fa-f]+|[\w]+);)"); r.format = entityFormat; m_rules.append(r); } // HTML-Kommentare QTextCharFormat htmlCommentFormat; htmlCommentFormat.setForeground(QColor("#6A9955")); htmlCommentFormat.setFontItalic(true); m_multiLineCommentFormat = htmlCommentFormat; m_commentStartExpression = QRegularExpression(""); m_hasMultiLineComment = true; // ---- PHP-Tags hervorheben ---- m_phpTagFormat.setForeground(QColor("#C586C0")); m_phpTagFormat.setFontWeight(QFont::Bold); // ---- PHP-spezifische Regeln ---- m_phpStringFormat.setForeground(QColor("#CE9178")); m_phpCommentFormat.setForeground(QColor("#6A9955")); m_phpCommentFormat.setFontItalic(true); // Keywords QTextCharFormat kwFormat; kwFormat.setForeground(QColor("#569CD6")); kwFormat.setFontWeight(QFont::Bold); const QStringList phpKeywords = { "abstract","and","array","as","break","callable","case","catch", "class","clone","const","continue","declare","default","die","do", "echo","else","elseif","empty","enddeclare","endfor","endforeach", "endif","endswitch","endwhile","enum","extends","final","finally", "fn","for","foreach","function","global","goto","if","implements", "include","include_once","instanceof","insteadof","interface", "isset","list","match","namespace","new","or","print","private", "protected","public","readonly","require","require_once","return", "static","switch","throw","trait","try","unset","use","var", "while","xor","yield","null","true","false","NULL","TRUE","FALSE" }; for (const QString &kw : phpKeywords) { HighlightRule r; r.pattern = QRegularExpression(QString("\\b%1\\b").arg(kw)); r.format = kwFormat; m_phpRules.append(r); } // Variablen $var QTextCharFormat varFormat; varFormat.setForeground(QColor("#9CDCFE")); { HighlightRule r; r.pattern = QRegularExpression(R"(\$[\w]+)"); r.format = varFormat; m_phpRules.append(r); } // Strings { HighlightRule r; r.pattern = QRegularExpression(R"("(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*')"); r.format = m_phpStringFormat; m_phpRules.append(r); } // Zahlen QTextCharFormat numFormat; numFormat.setForeground(QColor("#B5CEA8")); { HighlightRule r; r.pattern = QRegularExpression(R"(\b\d+\.?\d*\b)"); r.format = numFormat; m_phpRules.append(r); } // Einzeilige Kommentare { HighlightRule r; r.pattern = QRegularExpression(R"((//|#)[^\n]*)"); r.format = m_phpCommentFormat; m_phpRules.append(r); } // Eingebaute Funktionen (Auswahl der häufigsten) QTextCharFormat builtinFormat; builtinFormat.setForeground(QColor("#DCDCAA")); const QStringList builtins = { "array_map","array_filter","array_keys","array_values","array_merge", "array_push","array_pop","array_shift","array_slice","array_splice", "count","strlen","substr","strpos","strtolower","strtoupper","trim", "ltrim","rtrim","explode","implode","str_replace","preg_match", "preg_replace","sprintf","printf","print_r","var_dump","isset", "empty","unset","intval","floatval","strval","is_array","is_string", "is_int","is_float","is_null","is_bool","is_numeric","date","time", "mktime","json_encode","json_decode","header","session_start", "htmlspecialchars","htmlentities","strip_tags","nl2br","round", "floor","ceil","abs","min","max","rand","in_array","array_key_exists", "sort","rsort","usort","ksort","krsort","ob_start","ob_get_clean" }; for (const QString &fn : builtins) { HighlightRule r; r.pattern = QRegularExpression(QString("\\b%1\\b").arg(fn)); r.format = builtinFormat; m_phpRules.append(r); } } void PhpHighlighter::highlightPhpRange(const QString &text, int start, int length) { if (length <= 0) { return; } const QString phpText = text.mid(start, length); for (const HighlightRule &rule : m_phpRules) { QRegularExpressionMatchIterator it = rule.pattern.globalMatch(phpText); while (it.hasNext()) { QRegularExpressionMatch match = it.next(); setFormat( start + static_cast(match.capturedStart()), static_cast(match.capturedLength()), rule.format ); } } static const QRegularExpression blockOpen(R"(/\*)"); static const QRegularExpression blockClose(R"(\*/)"); int searchFrom = 0; // Leere Zeile mitten im PHP-Blockkommentar — Zustand weitertragen if (phpText.trimmed().isEmpty()) { if (previousBlockState() == 3) { setFormat(start, length, m_phpCommentFormat); setCurrentBlockState(3); } return; } if (previousBlockState() == 3) { QRegularExpressionMatch closeMatch = blockClose.match(phpText, 0); if (closeMatch.hasMatch()) { const int end = static_cast(closeMatch.capturedStart()) + static_cast(closeMatch.capturedLength()); setFormat(start, end, m_phpCommentFormat); searchFrom = end; } else { setFormat(start, length, m_phpCommentFormat); setCurrentBlockState(3); return; } } while (searchFrom < phpText.length()) { QRegularExpressionMatch openMatch = blockOpen.match(phpText, searchFrom); if (!openMatch.hasMatch()) { break; } const int openPos = static_cast(openMatch.capturedStart()); QRegularExpressionMatch closeMatch = blockClose.match(phpText, openPos + 2); if (closeMatch.hasMatch()) { const int closeEnd = static_cast(closeMatch.capturedStart()) + static_cast(closeMatch.capturedLength()); setFormat(start + openPos, closeEnd - openPos, m_phpCommentFormat); searchFrom = closeEnd; } else { setFormat(start + openPos, length - openPos, m_phpCommentFormat); setCurrentBlockState(3); return; } } } void PhpHighlighter::highlightBlock(const QString &text) { // Zuerst HTML-Basis-Regeln auf den gesamten Text anwenden // (setzt auch den State für HTML Kommentare) SyntaxHighlighter::highlightBlock(text); // Block-Zustände: // 0 = HTML-Modus // 1 = HTML Kommentar (von Basisklasse verwaltet) // 2 = innerhalb PHP-Block (kein /* */ Kommentar) // 3 = innerhalb PHP /* */ Block-Kommentar // State 1 (HTML-Kommentar) wurde von der Basisklasse gesetzt — nicht überschreiben if (currentBlockState() == 1) { return; } // Wenn der vorherige Block ein HTML-Kommentar war und dieser noch nicht // abgeschlossen wurde, hat die Basisklasse das bereits korrekt behandelt. // Wir setzen nur dann auf 0 zurück wenn wir sicher nicht in HTML-Kommentar sind. if (previousBlockState() != 1) { setCurrentBlockState(0); } // Leere Zeile — Zustand weitertragen if (text.isEmpty()) { const int prev = previousBlockState(); if (prev == 2 || prev == 3) { setCurrentBlockState(prev); } return; } static const QRegularExpression phpOpen(R"(<\?(?:php|=)?\s?)", QRegularExpression::CaseInsensitiveOption); static const QRegularExpression phpClose(R"(\?>)"); int pos = 0; if (previousBlockState() == 2 || previousBlockState() == 3) { // Wir befinden uns bereits in einem PHP-Block (ggf. in einem Kommentar) QRegularExpressionMatch closeMatch = phpClose.match(text, 0); if (closeMatch.hasMatch()) { const int end = static_cast(closeMatch.capturedStart()) + static_cast(closeMatch.capturedLength()); highlightPhpRange(text, 0, end); setFormat(static_cast(closeMatch.capturedStart()), static_cast(closeMatch.capturedLength()), m_phpTagFormat); pos = end; setCurrentBlockState(0); } else { highlightPhpRange(text, 0, text.length()); if (currentBlockState() != 3) { setCurrentBlockState(2); } return; } } while (pos < text.length()) { QRegularExpressionMatch openMatch = phpOpen.match(text, pos); if (!openMatch.hasMatch()) { break; } const int openStart = static_cast(openMatch.capturedStart()); const int openEnd = openStart + static_cast(openMatch.capturedLength()); setFormat(openStart, static_cast(openMatch.capturedLength()), m_phpTagFormat); QRegularExpressionMatch closeMatch = phpClose.match(text, openEnd); if (closeMatch.hasMatch()) { const int closeStart = static_cast(closeMatch.capturedStart()); const int closeEnd = closeStart + static_cast(closeMatch.capturedLength()); highlightPhpRange(text, openEnd, closeStart - openEnd); setFormat(closeStart, static_cast(closeMatch.capturedLength()), m_phpTagFormat); pos = closeEnd; setCurrentBlockState(0); } else { highlightPhpRange(text, openEnd, text.length() - openEnd); if (currentBlockState() != 3) { setCurrentBlockState(2); } return; } } }