Files
BareCode/src/highlighter/SyntaxHighlighter.cpp
Dany Thinnes 53608725aa - Fehler in mehrzeiligen Kommentaren behoben
- Klammerpaare werden farblich hervorgehoben
- Beim einrücken oder ausrücken eines markierten Blocks wird die Zeile, in der der Coursor steht, nicht mehr mitgerückt
- Beim eintippen von Variablen wird eine Liste mit bereits deklarierten Variablen angezeigt
2026-08-21 22:58:08 +02:00

590 lines
22 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
#include "SyntaxHighlighter.h"
#include <QTextDocument>
// ===========================================================================
// 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<int>(match.capturedStart()),
static_cast<int>(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<int>(endMatch.capturedStart())
+ static_cast<int>(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<int>(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<int>(m.capturedStart()) : -1;
}
while (startIndex >= 0)
{
QRegularExpressionMatch endMatch =
m_commentEndExpression.match(text, startIndex);
int commentLength = 0;
if (endMatch.hasMatch())
{
commentLength = static_cast<int>(endMatch.capturedStart())
- startIndex
+ static_cast<int>(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<int>(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 <div </div />
QTextCharFormat tagFormat;
tagFormat.setForeground(QColor("#569CD6"));
{ HighlightRule r; r.pattern = QRegularExpression(R"(</?[\w:-]+)"); r.format = tagFormat; m_rules.append(r); }
{ 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"(<!DOCTYPE[^>]*>)", QRegularExpression::CaseInsensitiveOption); r.format = doctypeFormat; m_rules.append(r); }
// Entities &amp; &#123;
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_commentEndExpression = 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 <?php ?>) ----
QTextCharFormat tagFormat;
tagFormat.setForeground(QColor("#569CD6"));
{ HighlightRule r; r.pattern = QRegularExpression(R"(</?[\w:-]+)"); r.format = tagFormat; m_rules.append(r); }
{ 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_commentEndExpression = 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<int>(match.capturedStart()),
static_cast<int>(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<int>(closeMatch.capturedStart())
+ static_cast<int>(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<int>(openMatch.capturedStart());
QRegularExpressionMatch closeMatch = blockClose.match(phpText, openPos + 2);
if (closeMatch.hasMatch())
{
const int closeEnd = static_cast<int>(closeMatch.capturedStart())
+ static_cast<int>(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<int>(closeMatch.capturedStart())
+ static_cast<int>(closeMatch.capturedLength());
highlightPhpRange(text, 0, end);
setFormat(static_cast<int>(closeMatch.capturedStart()),
static_cast<int>(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<int>(openMatch.capturedStart());
const int openEnd = openStart + static_cast<int>(openMatch.capturedLength());
setFormat(openStart, static_cast<int>(openMatch.capturedLength()), m_phpTagFormat);
QRegularExpressionMatch closeMatch = phpClose.match(text, openEnd);
if (closeMatch.hasMatch())
{
const int closeStart = static_cast<int>(closeMatch.capturedStart());
const int closeEnd = closeStart + static_cast<int>(closeMatch.capturedLength());
highlightPhpRange(text, openEnd, closeStart - openEnd);
setFormat(closeStart, static_cast<int>(closeMatch.capturedLength()), m_phpTagFormat);
pos = closeEnd;
setCurrentBlockState(0);
}
else
{
highlightPhpRange(text, openEnd, text.length() - openEnd);
if (currentBlockState() != 3)
{
setCurrentBlockState(2);
}
return;
}
}
}