#include "QtHighlighter.h" #include #include #include #include #include #include #include "ColorScheme.h" #include "FileSystem.h" #include "ResourcePaths.h" #include "TextAccess.h" #include "logging.h" #include "tracing.h" #include "utility.h" std::map> QtHighlighter::s_highlightingRules; std::map QtHighlighter::s_charFormats; std::string QtHighlighter::highlightTypeToString(QtHighlighter::HighlightType type) { switch (type) { case HighlightType::COMMENT: return "comment"; case HighlightType::DIRECTIVE: return "directive"; case HighlightType::FUNCTION: return "function"; case HighlightType::KEYWORD: return "keyword"; case HighlightType::NUMBER: return "number"; case HighlightType::QUOTATION: return "quotation"; case HighlightType::TEXT: return "text"; case HighlightType::TYPE: return "type"; } return "text"; } QtHighlighter::HighlightType QtHighlighter::highlightTypeFromString(const std::string typeStr) { const std::array types = { HighlightType::COMMENT, HighlightType::DIRECTIVE, HighlightType::FUNCTION, HighlightType::KEYWORD, HighlightType::NUMBER, HighlightType::QUOTATION, HighlightType::TEXT, HighlightType::TYPE}; for (HighlightType type: types) { if (typeStr == highlightTypeToString(type)) { return type; } } return HighlightType::TEXT; } void QtHighlighter::loadHighlightingRules() { ColorScheme* scheme = ColorScheme::getInstance().get(); const std::array types = { HighlightType::COMMENT, HighlightType::DIRECTIVE, HighlightType::FUNCTION, HighlightType::KEYWORD, HighlightType::NUMBER, HighlightType::QUOTATION, HighlightType::TEXT, HighlightType::TYPE}; s_charFormats.clear(); for (HighlightType type: types) { QTextCharFormat format; format.setForeground(QColor(scheme->getSyntaxColor(highlightTypeToString(type)).c_str())); s_charFormats.emplace(type, format); } for (const FilePath path: FileSystem::getFilePathsFromDirectory( ResourcePaths::getSyntaxHighlightingRulesPath(), {L".rules"})) { std::wstring language = path.withoutExtension().fileName(); std::shared_ptr textAccess = TextAccess::createFromFile(path); QJsonParseError error; QJsonDocument doc = QJsonDocument::fromJson( QString::fromStdString(textAccess->getText()).toUtf8(), &error); if (doc.isNull() || !doc.isArray()) { LOG_ERROR_STREAM( << "Highlinghting rules in \"" << path.str() << "\" couldn't be parsed as JSON: " "offset " << error.offset << " - " << error.errorString().toStdString()); continue; } std::vector rules; for (QJsonValueRef value: doc.array()) { if (!value.isObject()) { continue; } QJsonObject ruleObj = value.toObject(); HighlightType type = highlightTypeFromString( ruleObj.value(QStringLiteral("type")).toString().toStdString()); bool priority = ruleObj.value(QStringLiteral("priority")).toBool(); QJsonArray patterns = ruleObj.value(QStringLiteral("patterns")).toArray(); for (QJsonValueRef pattern: patterns) { if (pattern.isString()) { rules.push_back(HighlightingRule(type, QRegExp(pattern.toString()), priority)); } } QJsonObject range = ruleObj.value(QStringLiteral("range")).toObject(); if (!range.empty()) { rules.push_back(HighlightingRule( type, QRegExp(range.value("start").toString()), priority, true)); rules.push_back( HighlightingRule(type, QRegExp(range.value("end").toString()), priority, true)); } } s_highlightingRules.emplace(language, rules); } } void QtHighlighter::clearHighlightingRules() { s_highlightingRules.clear(); } QtHighlighter::QtHighlighter(QTextDocument* document, const std::wstring& language) : m_document(document) { if (!s_highlightingRules.size()) { loadHighlightingRules(); } const auto it = s_highlightingRules.find(language); if (it != s_highlightingRules.end()) { m_highlightingRules = it->second; } } void QtHighlighter::highlightDocument() { TRACE(); QTextDocument* doc = document(); size_t docStart = 0; size_t docEnd = 0; for (int i = 0; i < doc->blockCount(); i++) { docEnd += doc->findBlockByLineNumber(i).length(); } if (docEnd > 0) { docEnd -= 1; } applyFormat(docStart, docEnd, s_charFormats[HighlightType::TEXT]); m_highlightedLines.clear(); m_highlightedLines.resize(document()->blockCount(), false); if (m_highlightingRules.empty()) { return; } std::vector singleLineRules; for (const HighlightingRule& rule: m_highlightingRules) { if (rule.priority && !rule.multiLine) { singleLineRules.emplace_back(rule); } } createRanges(doc, singleLineRules); } void QtHighlighter::highlightRange(int startLine, int endLine) { if (startLine < 0 || endLine < 0 || startLine > endLine || endLine > int(m_highlightedLines.size())) { return; } bool hasUnhighlightedLines = false; for (int i = startLine; i <= endLine; i++) { if (!m_highlightedLines[i]) { hasUnhighlightedLines = true; break; } } if (!hasUnhighlightedLines) { return; } QTextDocument* doc = document(); QTextBlock start = doc->findBlockByLineNumber(startLine); QTextBlock end = doc->findBlockByLineNumber(endLine + 1); const HighlightingRule* singleLineCommentRule = nullptr; const HighlightingRule* quotationRule = nullptr; for (const HighlightingRule& rule: m_highlightingRules) { if (rule.type == HighlightType::COMMENT && !rule.multiLine) { singleLineCommentRule = &rule; } else if (rule.type == HighlightType::QUOTATION) { quotationRule = &rule; } } int index = startLine; for (QTextBlock it = start; it != end; it = it.next()) { if (!m_highlightedLines[index]) { applyFormat( it.position(), it.position() + it.length() - 1, s_charFormats[HighlightType::TEXT]); for (const HighlightingRule& rule: m_highlightingRules) { if (rule.multiLine) { continue; } if (rule.priority) { formatBlockIfInRange(it, rule.type, &m_singleLineRanges); } else { formatBlockForRule(it, rule, &m_singleLineRanges); } } if (m_multiLineRanges.size()) { formatBlockIfInRange(it, &m_multiLineRanges); } } index++; } for (int i = startLine; i <= endLine; i++) { m_highlightedLines[i] = true; } } void QtHighlighter::rehighlightLines(const std::vector& lines) { for (int line: lines) { if (line >= 0 && line < int(m_highlightedLines.size())) { m_highlightedLines[line] = false; } } } void QtHighlighter::applyFormat(int startPosition, int endPosition, const QTextCharFormat& format) { QTextCursor cursor(document()); cursor.setPosition(startPosition); cursor.setPosition(endPosition, QTextCursor::KeepAnchor); cursor.setCharFormat(format); } QTextCharFormat QtHighlighter::getFormat(int startPosition, int endPosition) const { QTextCursor cursor(document()); cursor.setPosition(endPosition); return cursor.charFormat(); } void QtHighlighter::createRanges(QTextDocument* doc, const std::vector& singleLineRules) { m_singleLineRanges.clear(); m_multiLineRanges.clear(); for (QTextBlock it = doc->begin(); it != doc->end(); it = it.next()) { for (const HighlightingRule& rule: singleLineRules) { utility::append(m_singleLineRanges, getRangesForRule(it, rule)); } } // remove ranges starting inside others { std::map, size_t> sortedRangesToIndex; for (size_t i = 0; i < m_singleLineRanges.size(); i++) { const std::tuple& range = m_singleLineRanges[i]; sortedRangesToIndex.emplace(std::make_pair(std::get<1>(range), std::get<2>(range)), i); } std::set indicesToErase; const std::pair* topRange = nullptr; for (const auto& p: sortedRangesToIndex) { if (topRange && p.first.first <= topRange->second) { indicesToErase.insert(p.second); } else { topRange = &p.first; } } for (std::set::const_reverse_iterator it = indicesToErase.rbegin(); it != indicesToErase.rend(); it++) { m_singleLineRanges.erase(m_singleLineRanges.begin() + *it); } } m_multiLineRanges = createMultiLineRanges(doc, &m_singleLineRanges); } std::vector> QtHighlighter::createMultiLineRanges( QTextDocument* doc, std::vector>* ranges) { std::vector> multiLineRanges; const HighlightingRule* startRule = nullptr; for (const HighlightingRule& rule: m_highlightingRules) { if (rule.priority && rule.multiLine) { if (!startRule) { startRule = &rule; } else if (rule.type == startRule->type) { utility::append( multiLineRanges, createMultiLineRangesForRules(doc, ranges, startRule, &rule)); startRule = nullptr; } } } return multiLineRanges; } std::vector> QtHighlighter::createMultiLineRangesForRules( QTextDocument* doc, std::vector>* ranges, const HighlightingRule* startRule, const HighlightingRule* endRule) { std::vector> multiLineRanges; QTextCursor cursorStart(doc); QTextCursor cursorEnd(doc); while (true) { while (true) { cursorStart = document()->find(startRule->pattern, cursorStart); if (cursorStart.isNull()) { break; } if (!isInRange(cursorStart.selectionEnd() - 1, *ranges)) { break; } else { cursorStart.setPosition(cursorStart.selectionEnd() + 1); } } if (cursorStart.isNull()) { break; } cursorEnd = document()->find(endRule->pattern, cursorStart); if (cursorEnd.isNull()) { break; } multiLineRanges.emplace_back( std::make_tuple(startRule->type, cursorStart.selectionStart(), cursorEnd.position())); cursorStart = cursorEnd; } return multiLineRanges; } QtHighlighter::HighlightingRule::HighlightingRule() {} QtHighlighter::HighlightingRule::HighlightingRule( HighlightType type, const QRegExp& regExp, bool priority, bool multiLine) : type(type), pattern(regExp), priority(priority), multiLine(multiLine) { } bool QtHighlighter::isInRange(int pos, const std::vector>& ranges) const { for (const std::tuple& range: ranges) { if (pos >= std::get<1>(range) && pos <= std::get<2>(range)) { return true; } } return false; } std::vector> QtHighlighter::getRangesForRule( const QTextBlock& block, const HighlightingRule& rule) const { const int pos = block.position(); const QString text = block.text(); QRegExp expression(rule.pattern); int index = expression.indexIn(text); std::vector> ranges; while (index >= 0) { const int length = expression.matchedLength(); if (expression.capturedTexts().size() > 1) { const QString cap = expression.capturedTexts()[1]; const int start = text.indexOf(cap, index); ranges.push_back(std::make_tuple(rule.type, pos + start, pos + start + cap.length())); } else { ranges.push_back(std::make_tuple(rule.type, pos + index, pos + index + length)); } index = expression.indexIn(block.text(), index + length); } return ranges; } void QtHighlighter::formatBlockForRule( const QTextBlock& block, const HighlightingRule& rule, std::vector>* ranges) { if (s_charFormats.find(rule.type) == s_charFormats.end()) { return; } const QTextCharFormat& format = s_charFormats.find(rule.type)->second; QRegExp expression(rule.pattern); int pos = block.position(); int index = expression.indexIn(block.text()); while (index >= 0) { int length = expression.matchedLength(); if (!isInRange(pos + index, *ranges)) { applyFormat(pos + index, pos + index + length, format); } index = expression.indexIn(block.text(), index + length); } } void QtHighlighter::formatBlockIfInRange( const QTextBlock& block, HighlightType type, std::vector>* ranges) { int startPos = block.position(); int endPos = startPos + block.length() - 1; if (s_charFormats.find(type) == s_charFormats.end()) { return; } const QTextCharFormat& format = s_charFormats.find(type)->second; for (auto range: *ranges) { if (type == std::get<0>(range)) { int start = std::max(std::get<1>(range), startPos); int end = std::min(std::get<2>(range), endPos); if (start <= end) { applyFormat(start, end, format); } } } } void QtHighlighter::formatBlockIfInRange( const QTextBlock& block, std::vector>* ranges) { int startPos = block.position(); int endPos = startPos + block.length() - 1; for (auto range: *ranges) { HighlightType type = std::get<0>(range); if (s_charFormats.find(type) == s_charFormats.end()) { continue; } const QTextCharFormat& format = s_charFormats.find(type)->second; int start = std::max(std::get<1>(range), startPos); int end = std::min(std::get<2>(range), endPos); if (start <= end) { applyFormat(start, end, format); } } } QTextDocument* QtHighlighter::document() const { return m_document; }