Sandboxie/SandboxiePlus/SandMan/Helpers/IniHighlighter.cpp

256 lines
9.7 KiB
C++

#include "stdafx.h"
#include "IniHighlighter.h"
CIniHighlighter::CIniHighlighter(bool bDarkMode, QTextDocument *parent)
: QSyntaxHighlighter(parent)
{
// Define colors for light and dark mode
QColor blue = bDarkMode ? QColor("#87CEFA") : QColor("#0000FF"); // Lighter blue for dark mode
QColor green = bDarkMode ? QColor("#90EE90") : QColor("#008000"); // Lighter green for dark mode
QColor darkRed = bDarkMode ? QColor("#FF6347") : QColor("#800000"); // Lighter red for dark mode
QColor red = bDarkMode ? QColor("#FF4500") : QColor("#FF0000"); // Brighter red for dark mode
QColor black = bDarkMode ? QColor("#DCDCDC") : QColor("#000000"); // Light gray for dark mode
QColor brown = bDarkMode ? QColor("#F4A460") : QColor("#A52A2A"); // Light brown for dark mode
QColor purple = bDarkMode ? QColor("#DA70D6") : QColor("#800080"); // Brighter purple for dark mode
QColor gray = bDarkMode ? QColor("#A9A9A9") : QColor("#808080"); // Lighter gray for dark mode
HighlightRule rule;
// Section headers: [Section]
sectionFormat.setForeground(blue);
sectionFormat.setFontWeight(QFont::Bold);
rule.pattern = QRegularExpression("^\\s*\\[.*\\]\\s*$");
rule.format = sectionFormat;
highlightRules.append(rule);
// Comments: ; comment or # comment
commentFormat.setForeground(green);
rule.pattern = QRegularExpression("^\\s*[;#].*");
rule.format = commentFormat;
highlightRules.append(rule);
// Keys: key=
keyFormat.setForeground(darkRed);
rule.pattern = QRegularExpression("^[\\w\\.]+(?=\\s*=)");
rule.format = keyFormat;
highlightRules.append(rule);
// Equals sign: =
equalsFormat.setForeground(red);
rule.pattern = QRegularExpression("=");
rule.format = equalsFormat;
highlightRules.append(rule);
// Values: =value
valueFormat.setForeground(black);
rule.pattern = QRegularExpression("(?<=\\=).*");
rule.format = valueFormat;
highlightRules.append(rule);
// Initialize formats for value prefix and first comma
valuePrefixFormat.setForeground(blue);
firstCommaFormat.setForeground(red);
#ifdef INI_WITH_JSON
// Initialize JSON formats
jsonKeyFormat.setForeground(brown);
jsonStringFormat.setForeground(black);
jsonNumberFormat.setForeground(blue);
jsonBoolNullFormat.setForeground(purple);
jsonBracesFormat.setForeground(gray);
jsonColonFormat.setForeground(red);
jsonCommaFormat.setForeground(red);
// 1. JSON Colon: Match colons not preceded by backslash
HighlightRule jsonRule;
jsonRule.pattern = QRegularExpression(R"((?<!\\):)");
jsonRule.format = jsonColonFormat;
jsonHighlightRules.append(jsonRule);
// 2. JSON Comma: Match commas not preceded by backslash
jsonRule.pattern = QRegularExpression(R"((?<!\\),)");
jsonRule.format = jsonCommaFormat;
jsonHighlightRules.append(jsonRule);
// 3. JSON Keys: "key":
jsonRule.pattern = QRegularExpression(R"("(?:(?:\\.)|[^"\\])*"(?=\s*:))");
jsonRule.format = jsonKeyFormat;
jsonHighlightRules.append(jsonRule);
// 4. JSON Strings: "value" (excluding keys)
jsonRule.pattern = QRegularExpression(R"("(?:(?:\\.)|[^"\\])*"(?!\s*:))");
jsonRule.format = jsonStringFormat;
jsonHighlightRules.append(jsonRule);
// 5. JSON Numbers: 123, 45.67
jsonRule.pattern = QRegularExpression(R"(\b-?\d+(\.\d+)?\b)");
jsonRule.format = jsonNumberFormat;
jsonHighlightRules.append(jsonRule);
// 6. JSON Booleans and Null: true, false, null
jsonRule.pattern = QRegularExpression(R"(\b(true|false|null)\b)", QRegularExpression::CaseInsensitiveOption);
jsonRule.format = jsonBoolNullFormat;
jsonHighlightRules.append(jsonRule);
// 7. JSON Braces and Brackets: { }, [ ]
jsonRule.pattern = QRegularExpression(R"([\{\}\[\]])");
jsonRule.format = jsonBracesFormat;
jsonHighlightRules.append(jsonRule);
#endif
}
CIniHighlighter::~CIniHighlighter()
{
}
void CIniHighlighter::highlightBlock(const QString &text)
{
// First, reset all formatting
setFormat(0, text.length(), QTextCharFormat());
// 1. Check if the entire line is a comment
QRegularExpression commentRegex(R"(^\s*[;#].*)");
QRegularExpressionMatch commentMatch = commentRegex.match(text);
if (commentMatch.hasMatch()) {
setFormat(0, text.length(), commentFormat);
return; // Skip other rules
}
// 2. Apply INI highlighting rules (section, key, equals, value)
for (const HighlightRule &rule : qAsConst(highlightRules)) {
// Skip the comment rule as it's already handled
if (rule.format.foreground() == commentFormat.foreground())
continue;
QRegularExpressionMatchIterator matchIterator = rule.pattern.globalMatch(text);
while (matchIterator.hasNext()) {
QRegularExpressionMatch match = matchIterator.next();
int start = match.capturedStart();
int length = match.capturedLength();
setFormat(start, length, rule.format);
}
}
// 3. Process the value part for value prefixes and first comma
// Find the position of '=' to identify the start of the value
int equalsIndex = text.indexOf('=');
if (equalsIndex != -1) {
// Start position of the value (after '=')
int valueStart = equalsIndex + 1;
QString valueText = text.mid(valueStart).trimmed();
// Iterate through the value to find the first comma outside of {}
int braceLevel = 0;
int firstRelevantCommaIndex = -1;
for (int i = 0; i < valueText.length(); ++i) {
QChar currentChar = valueText[i];
if (currentChar == '{') {
braceLevel++;
} else if (currentChar == '}') {
if (braceLevel > 0)
braceLevel--;
} else if (currentChar == ',' && braceLevel == 0) {
firstRelevantCommaIndex = i;
break;
}
}
if (firstRelevantCommaIndex != -1) {
// Position of the first comma relative to the entire line
int commaPos = valueStart + firstRelevantCommaIndex;
// Highlight text before the first comma in blue
if (firstRelevantCommaIndex > 0) {
setFormat(valueStart, firstRelevantCommaIndex, valuePrefixFormat);
}
// Highlight the first comma in red
setFormat(commaPos, 1, firstCommaFormat);
}
#ifdef INI_WITH_JSON
bool inString = false;
int stringStart = -1;
for (int i = firstRelevantCommaIndex != -1 ? firstRelevantCommaIndex + 1 : 0; i < valueText.length(); ++i) {
QChar currentChar = valueText[i];
if (currentChar == '\"') {
// Check if the quote is escaped
bool escaped = false;
int backslashCount = 0;
int j = i - 1;
while (j >= 0 && valueText[j] == '\\') {
backslashCount++;
j--;
}
if (backslashCount % 2 == 1)
escaped = true;
if (!escaped) {
if (!inString) {
inString = true;
stringStart = valueStart + i;
}
else {
inString = false;
// Apply string formatting from stringStart to current position
int length = (valueStart + i + 1) - stringStart;
setFormat(stringStart, length, jsonStringFormat);
}
}
}
// Apply colon and comma formatting only if not inside a string
if (!inString) {
if (currentChar == ':') {
setFormat(valueStart + i, 1, jsonColonFormat);
}
else if (currentChar == ',') {
setFormat(valueStart + i, 1, jsonCommaFormat);
}
}
}
// If still inside a string (unclosed), format till end
if (inString && stringStart != -1) {
int length = text.length() - stringStart;
setFormat(stringStart, length, jsonStringFormat);
}
// 4. Apply JSON Key Formatting within JSON Substrings
// Find all JSON substrings and apply key formatting
int current = 0;
while (current < valueText.length()) {
int startBrace = valueText.indexOf('{', current);
if (startBrace == -1)
break;
int braceCounter = 1;
int endBrace = startBrace + 1;
while (endBrace < valueText.length() && braceCounter > 0) {
if (valueText[endBrace] == '{')
braceCounter++;
else if (valueText[endBrace] == '}')
braceCounter--;
endBrace++;
}
if (braceCounter == 0) {
// Found a JSON substring from startBrace to endBrace-1
QString jsonString = valueText.mid(startBrace, endBrace - startBrace);
QRegularExpression keyRegex(R"("(?:(?:\\.)|[^"\\])*"(?=\s*:))");
QRegularExpressionMatchIterator keyMatches = keyRegex.globalMatch(jsonString);
while (keyMatches.hasNext()) {
QRegularExpressionMatch keyMatch = keyMatches.next();
int keyStart = valueStart + startBrace + keyMatch.capturedStart();
int keyLength = keyMatch.capturedLength();
setFormat(keyStart, keyLength, jsonKeyFormat);
}
current = endBrace;
}
else {
break;
}
}
#endif
}
}