// Copyright (C) 2019 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only WITH Qt-GPL-exception-1.0 #include "highlighter.h" #include "tabsettings.h" #include "textdocumentlayout.h" #include #include #include #include #include #include #include #include #include #include #include #include #include #include using namespace Utils; namespace TextEditor { static Q_LOGGING_CATEGORY(highlighterLog, "qtc.editor.highlighter", QtWarningMsg) TextStyle categoryForTextStyle(int style) { switch (style) { case KSyntaxHighlighting::Theme::Normal: return C_TEXT; case KSyntaxHighlighting::Theme::Keyword: return C_KEYWORD; case KSyntaxHighlighting::Theme::Function: return C_FUNCTION; case KSyntaxHighlighting::Theme::Variable: return C_LOCAL; case KSyntaxHighlighting::Theme::ControlFlow: return C_KEYWORD; case KSyntaxHighlighting::Theme::Operator: return C_OPERATOR; case KSyntaxHighlighting::Theme::BuiltIn: return C_PRIMITIVE_TYPE; case KSyntaxHighlighting::Theme::Extension: return C_GLOBAL; case KSyntaxHighlighting::Theme::Preprocessor: return C_PREPROCESSOR; case KSyntaxHighlighting::Theme::Attribute: return C_LOCAL; case KSyntaxHighlighting::Theme::Char: return C_STRING; case KSyntaxHighlighting::Theme::SpecialChar: return C_STRING; case KSyntaxHighlighting::Theme::String: return C_STRING; case KSyntaxHighlighting::Theme::VerbatimString: return C_STRING; case KSyntaxHighlighting::Theme::SpecialString: return C_STRING; case KSyntaxHighlighting::Theme::Import: return C_PREPROCESSOR; case KSyntaxHighlighting::Theme::DataType: return C_TYPE; case KSyntaxHighlighting::Theme::DecVal: return C_NUMBER; case KSyntaxHighlighting::Theme::BaseN: return C_NUMBER; case KSyntaxHighlighting::Theme::Float: return C_NUMBER; case KSyntaxHighlighting::Theme::Constant: return C_KEYWORD; case KSyntaxHighlighting::Theme::Comment: return C_COMMENT; case KSyntaxHighlighting::Theme::Documentation: return C_DOXYGEN_COMMENT; case KSyntaxHighlighting::Theme::Annotation: return C_DOXYGEN_TAG; case KSyntaxHighlighting::Theme::CommentVar: return C_DOXYGEN_TAG; case KSyntaxHighlighting::Theme::RegionMarker: return C_PREPROCESSOR; case KSyntaxHighlighting::Theme::Information: return C_WARNING; case KSyntaxHighlighting::Theme::Warning: return C_WARNING; case KSyntaxHighlighting::Theme::Alert: return C_ERROR; case KSyntaxHighlighting::Theme::Error: return C_ERROR; case KSyntaxHighlighting::Theme::Others: return C_TEXT; } return C_TEXT; } Highlighter::Highlighter() { setTextFormatCategories(QMetaEnum::fromType().keyCount(), &categoryForTextStyle); } Highlighter::~Highlighter() = default; static bool isOpeningParenthesis(QChar c) { return c == QLatin1Char('{') || c == QLatin1Char('[') || c == QLatin1Char('('); } static bool isClosingParenthesis(QChar c) { return c == QLatin1Char('}') || c == QLatin1Char(']') || c == QLatin1Char(')'); } void Highlighter::highlightBlock(const QString &text) { if (!definition().isValid()) { formatSpaces(text); return; } QTextBlock block = currentBlock(); const QTextBlock previousBlock = block.previous(); TextBlockUserData::setBraceDepth(block, TextBlockUserData::braceDepth(previousBlock)); KSyntaxHighlighting::State previousLineState = TextBlockUserData::syntaxState(previousBlock); KSyntaxHighlighting::State oldState = TextBlockUserData::syntaxState(block); TextBlockUserData::setFoldingStartIncluded(block, false); TextBlockUserData::setFoldingEndIncluded(block, false); KSyntaxHighlighting::State state = highlightLine(text, previousLineState); if (oldState != state) { TextBlockUserData::setSyntaxState(block, state); // Toggles the LSB of current block's userState. It forces rehighlight of next block. setCurrentBlockState(currentBlockState() ^ 1); } Parentheses parentheses; int pos = 0; for (const QChar &c : text) { if (isOpeningParenthesis(c)) parentheses.push_back(Parenthesis(Parenthesis::Opened, c, pos)); else if (isClosingParenthesis(c)) parentheses.push_back(Parenthesis(Parenthesis::Closed, c, pos)); pos++; } TextBlockUserData::setParentheses(currentBlock(), parentheses); const QTextBlock nextBlock = block.next(); if (nextBlock.isValid()) TextBlockUserData::setFoldingIndent(nextBlock, TextBlockUserData::braceDepth(block)); formatSpaces(text); } void Highlighter::applyFormat(int offset, int length, const KSyntaxHighlighting::Format &format) { const KSyntaxHighlighting::Theme defaultTheme; QTextCharFormat qformat = formatForCategory(format.textStyle()); if (format.hasTextColor(defaultTheme)) { const QColor textColor = format.textColor(defaultTheme); if (format.hasBackgroundColor(defaultTheme)) { const QColor backgroundColor = format.hasBackgroundColor(defaultTheme); if (StyleHelper::isReadableOn(backgroundColor, textColor)) { qformat.setForeground(textColor); qformat.setBackground(backgroundColor); } else if (StyleHelper::isReadableOn(qformat.background().color(), textColor)) { qformat.setForeground(textColor); } } else if (StyleHelper::isReadableOn(qformat.background().color(), textColor)) { qformat.setForeground(textColor); } } else if (format.hasBackgroundColor(defaultTheme)) { const QColor backgroundColor = format.hasBackgroundColor(defaultTheme); if (StyleHelper::isReadableOn(backgroundColor, qformat.foreground().color())) qformat.setBackground(backgroundColor); } if (format.isBold(defaultTheme)) qformat.setFontWeight(QFont::Bold); if (format.isItalic(defaultTheme)) qformat.setFontItalic(true); if (format.isUnderline(defaultTheme)) qformat.setFontUnderline(true); if (format.isStrikeThrough(defaultTheme)) qformat.setFontStrikeOut(true); setFormat(offset, length, qformat); } void Highlighter::applyFolding(int offset, int length, KSyntaxHighlighting::FoldingRegion region) { if (!region.isValid()) return; QTextBlock block = currentBlock(); const QString &text = block.text(); const bool fromStart = TabSettings::firstNonSpace(text) == offset; const bool toEnd = (offset + length) == (text.length() - TabSettings::trailingWhitespaces(text)); if (region.type() == KSyntaxHighlighting::FoldingRegion::Begin) { const int newBraceDepth = TextBlockUserData::braceDepth(block) + 1; TextBlockUserData::setBraceDepth(block, newBraceDepth); qCDebug(highlighterLog) << "Found folding start from '" << offset << "' to '" << length << "' resulting in the bracedepth '" << newBraceDepth << "' in :"; qCDebug(highlighterLog) << text; // if there is only a folding begin marker in the line move the current block into the fold if (fromStart && toEnd && length <= 1) { TextBlockUserData::setFoldingIndent(block, TextBlockUserData::braceDepth(block)); TextBlockUserData::setFoldingStartIncluded(block, true); } } else if (region.type() == KSyntaxHighlighting::FoldingRegion::End) { const int newBraceDepth = qMax(0, TextBlockUserData::braceDepth(block) - 1); qCDebug(highlighterLog) << "Found folding end from '" << offset << "' to '" << length << "' resulting in the bracedepth '" << newBraceDepth << "' in :"; qCDebug(highlighterLog) << text; TextBlockUserData::setBraceDepth(block, newBraceDepth); // if the folding end is at the end of the line move the current block into the fold if (toEnd) TextBlockUserData::setFoldingEndIncluded(block, true); else TextBlockUserData::setFoldingIndent(block, TextBlockUserData::braceDepth(block)); } } } // TextEditor