aboutsummaryrefslogtreecommitdiffstats
path: root/src/plugins
diff options
context:
space:
mode:
authorChristian Kandeler <christian.kandeler@qt.io>2023-01-31 15:08:20 +0100
committerChristian Kandeler <christian.kandeler@qt.io>2023-02-02 09:52:55 +0000
commit209e3d0e66972ea63c7496a731b1757216641691 (patch)
treeeae0fc029df793a1dff30cb60c2bf9f937fe6873 /src/plugins
parent6570895c0b388801285c5a5a72c21e7e4a0f07ea (diff)
CppEditor: Fully handle raw string literals in the syntax highlighter
As of a3af941adf90bde0a2700f250f8d102cbdea3267, the built-in highlighter can properly handle multi-line raw string literals, so we don't need to abuse the semantic highlighter for this anymore. Fixes: QTCREATORBUG-26693 Fixes: QTCREATORBUG-28284 Change-Id: If644767dfa8a97294e84a541eea44143e8d1bb88 Reviewed-by: <github-actions-qt-creator@cristianadam.eu> Reviewed-by: David Schulz <david.schulz@qt.io>
Diffstat (limited to 'src/plugins')
-rw-r--r--src/plugins/clangcodemodel/clangdsemantichighlighting.cpp7
-rw-r--r--src/plugins/clangcodemodel/test/clangdtests.cpp9
-rw-r--r--src/plugins/cppeditor/cppeditor.qrc1
-rw-r--r--src/plugins/cppeditor/cppeditorplugin.cpp2
-rw-r--r--src/plugins/cppeditor/cpphighlighter.cpp174
-rw-r--r--src/plugins/cppeditor/cpphighlighter.h20
-rw-r--r--src/plugins/cppeditor/semantichighlighter.cpp66
-rw-r--r--src/plugins/cppeditor/testcases/highlightingtestcase.cpp7
-rw-r--r--src/plugins/texteditor/syntaxhighlighter.cpp11
-rw-r--r--src/plugins/texteditor/syntaxhighlighter.h1
10 files changed, 185 insertions, 113 deletions
diff --git a/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp b/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp
index 862bb30bf8a..771b9898679 100644
--- a/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp
+++ b/src/plugins/clangcodemodel/clangdsemantichighlighting.cpp
@@ -599,10 +599,9 @@ void ExtraHighlightingResultsCollector::collectFromNode(const ClangdAstNode &nod
if (node.kind().endsWith("Literal")) {
const bool isKeyword = node.kind() == "CXXBoolLiteral"
|| node.kind() == "CXXNullPtrLiteral";
- const bool isStringLike = !isKeyword && (node.kind().startsWith("String")
- || node.kind().startsWith("Character"));
- const TextStyle style = isKeyword ? C_KEYWORD : isStringLike ? C_STRING : C_NUMBER;
- insertResult(node, style);
+ if (!isKeyword && (node.kind().startsWith("String") || node.kind().startsWith("Character")))
+ return;
+ insertResult(node, isKeyword ? C_KEYWORD : C_NUMBER);
return;
}
if (node.role() == "type" && node.kind() == "Builtin") {
diff --git a/src/plugins/clangcodemodel/test/clangdtests.cpp b/src/plugins/clangcodemodel/test/clangdtests.cpp
index 14bdc064b61..a644b3a045d 100644
--- a/src/plugins/clangcodemodel/test/clangdtests.cpp
+++ b/src/plugins/clangcodemodel/test/clangdtests.cpp
@@ -693,10 +693,6 @@ void ClangdTestHighlighting::test_data()
QTest::addColumn<QList<int>>("expectedStyles");
QTest::addColumn<int>("expectedKind");
- QTest::newRow("string literal") << 1 << 24 << 1 << 34 << QList<int>{C_STRING} << 0;
- QTest::newRow("UTF-8 string literal") << 2 << 24 << 2 << 36 << QList<int>{C_STRING} << 0;
- QTest::newRow("raw string literal") << 3 << 24 << 4 << 9 << QList<int>{C_STRING} << 0;
- QTest::newRow("character literal") << 5 << 24 << 5 << 27 << QList<int>{C_STRING} << 0;
QTest::newRow("integer literal") << 23 << 24 << 23 << 25 << QList<int>{C_NUMBER} << 0;
QTest::newRow("float literal") << 24 << 24 << 24 << 28 << QList<int>{C_NUMBER} << 0;
QTest::newRow("function definition") << 45 << 5 << 45 << 13
@@ -1225,7 +1221,6 @@ void ClangdTestHighlighting::test_data()
QTest::newRow("triply nested template instantiation with spacing (closing angle bracket 4)")
<< 812 << 3 << 812 << 4
<< QList<int>{C_PUNCTUATION} << int(CppEditor::SemanticHighlighter::AngleBracketClose);
- QTest::newRow("cyrillic string") << 792 << 24 << 792 << 27 << QList<int>{C_STRING} << 0;
QTest::newRow("macro in struct") << 795 << 9 << 795 << 14
<< QList<int>{C_MACRO, C_DECLARATION} << 0;
QTest::newRow("#ifdef'ed out code") << 800 << 1 << 800 << 17
@@ -1248,10 +1243,6 @@ void ClangdTestHighlighting::test_data()
QTest::newRow("simple return") << 841 << 12 << 841 << 15 << QList<int>{C_LOCAL} << 0;
QTest::newRow("lambda parameter") << 847 << 49 << 847 << 52
<< QList<int>{C_PARAMETER, C_DECLARATION} << 0;
- QTest::newRow("string literal passed to macro from same file") << 853 << 32 << 853 << 38
- << QList<int>{C_STRING} << 0;
- QTest::newRow("string literal passed to macro from header file") << 854 << 32 << 854 << 38
- << QList<int>{C_STRING} << 0;
QTest::newRow("user-defined operator call") << 860 << 7 << 860 << 8
<< QList<int>{C_LOCAL} << 0;
QTest::newRow("const member as function argument") << 868 << 32 << 868 << 43
diff --git a/src/plugins/cppeditor/cppeditor.qrc b/src/plugins/cppeditor/cppeditor.qrc
index b66e10aeed6..c28111d21e6 100644
--- a/src/plugins/cppeditor/cppeditor.qrc
+++ b/src/plugins/cppeditor/cppeditor.qrc
@@ -3,5 +3,6 @@
<file>images/dark_qt_cpp.png</file>
<file>images/dark_qt_h.png</file>
<file>images/dark_qt_c.png</file>
+ <file>testcases/highlightingtestcase.cpp</file>
</qresource>
</RCC>
diff --git a/src/plugins/cppeditor/cppeditorplugin.cpp b/src/plugins/cppeditor/cppeditorplugin.cpp
index d596a1fe7e2..af6dcdb6403 100644
--- a/src/plugins/cppeditor/cppeditorplugin.cpp
+++ b/src/plugins/cppeditor/cppeditorplugin.cpp
@@ -34,6 +34,7 @@
#include "cppcompletion_test.h"
#include "cppdoxygen_test.h"
#include "cppheadersource_test.h"
+#include "cpphighlighter.h"
#include "cppincludehierarchy_test.h"
#include "cppinsertvirtualmethods.h"
#include "cpplocalsymbols_test.h"
@@ -566,6 +567,7 @@ QVector<QObject *> CppEditorPlugin::createTestObjects() const
new CodegenTest,
new CompilerOptionsBuilderTest,
new CompletionTest,
+ new CppHighlighterTest,
new FunctionUtilsTest,
new HeaderPathFilterTest,
new HeaderSourceTest,
diff --git a/src/plugins/cppeditor/cpphighlighter.cpp b/src/plugins/cppeditor/cpphighlighter.cpp
index 557d7441887..42982c43083 100644
--- a/src/plugins/cppeditor/cpphighlighter.cpp
+++ b/src/plugins/cppeditor/cpphighlighter.cpp
@@ -4,20 +4,27 @@
#include "cpphighlighter.h"
#include "cppdoxygen.h"
-#include "cppmodelmanager.h"
#include "cpptoolsreuse.h"
#include <texteditor/textdocumentlayout.h>
+#include <utils/textutils.h>
#include <cplusplus/SimpleLexer.h>
#include <cplusplus/Lexer.h>
+#include <QFile>
#include <QTextDocument>
+#include <QTextLayout>
+
+#ifdef WITH_TESTS
+#include <QtTest>
+#endif
-using namespace CppEditor;
using namespace TextEditor;
using namespace CPlusPlus;
+namespace CppEditor {
+
CppHighlighter::CppHighlighter(QTextDocument *document) :
SyntaxHighlighter(document)
{
@@ -38,8 +45,11 @@ void CppHighlighter::highlightBlock(const QString &text)
SimpleLexer tokenize;
tokenize.setLanguageFeatures(m_languageFeatures);
const QTextBlock prevBlock = currentBlock().previous();
- if (prevBlock.isValid())
- tokenize.setExpectedRawStringSuffix(TextDocumentLayout::expectedRawStringSuffix(prevBlock));
+ QByteArray inheritedRawStringSuffix;
+ if (prevBlock.isValid()) {
+ inheritedRawStringSuffix = TextDocumentLayout::expectedRawStringSuffix(prevBlock);
+ tokenize.setExpectedRawStringSuffix(inheritedRawStringSuffix);
+ }
int initialLexerState = lexerState;
const Tokens tokens = tokenize(text, initialLexerState);
@@ -84,6 +94,8 @@ void CppHighlighter::highlightBlock(const QString &text)
int previousTokenEnd = 0;
if (i != 0) {
+ inheritedRawStringSuffix.clear();
+
// mark the whitespaces
previousTokenEnd = tokens.at(i - 1).utf16charsBegin() +
tokens.at(i - 1).utf16chars();
@@ -148,7 +160,7 @@ void CppHighlighter::highlightBlock(const QString &text)
} else if (tk.is(T_NUMERIC_LITERAL)) {
setFormat(tk.utf16charsBegin(), tk.utf16chars(), formatForCategory(C_NUMBER));
} else if (tk.isStringLiteral() || tk.isCharLiteral()) {
- if (!highlightRawStringLiteral(text, tk)) {
+ if (!highlightRawStringLiteral(text, tk, QString::fromUtf8(inheritedRawStringSuffix))) {
setFormatWithSpaces(text, tk.utf16charsBegin(), tk.utf16chars(),
formatForCategory(C_STRING));
}
@@ -354,7 +366,8 @@ void CppHighlighter::highlightWord(QStringView word, int position, int length)
}
}
-bool CppHighlighter::highlightRawStringLiteral(QStringView _text, const Token &tk)
+bool CppHighlighter::highlightRawStringLiteral(QStringView text, const Token &tk,
+ const QString &inheritedSuffix)
{
// Step one: Does the lexer think this is a raw string literal?
switch (tk.kind()) {
@@ -368,37 +381,50 @@ bool CppHighlighter::highlightRawStringLiteral(QStringView _text, const Token &t
return false;
}
- // TODO: Remove on upgrade to Qt >= 5.14.
- const QString text = _text.toString();
+ // Step two: Try to find all the components (prefix/string/suffix). We might be in the middle
+ // of a multi-line literal, though, so prefix and/or suffix might be missing.
+ int delimiterOffset = -1;
+ int stringOffset = 0;
+ int stringLength = tk.utf16chars();
+ int endDelimiterOffset = -1;
+ QString expectedSuffix = inheritedSuffix;
+ [&] {
+ // If the "inherited" suffix is not empty, then this token is a string continuation and
+ // can therefore not start a new raw string literal.
+ // FIXME: The lexer starts the token at the first non-whitespace character, so
+ // we have to correct for that here.
+ if (!inheritedSuffix.isEmpty()) {
+ stringLength += tk.utf16charOffset;
+ return;
+ }
- // Step two: Find all the components. Bail out if we don't have a complete,
- // well-formed raw string literal.
- const int rOffset = text.indexOf(QLatin1String("R\""), tk.utf16charsBegin());
- if (rOffset == -1)
- return false;
- const int delimiterOffset = rOffset + 2;
- const int openParenOffset = text.indexOf('(', delimiterOffset);
- if (openParenOffset == -1)
- return false;
- const QStringView delimiter = text.mid(delimiterOffset, openParenOffset - delimiterOffset);
- if (text.at(tk.utf16charsEnd() - 1) != '"')
- return false;
- const int endDelimiterOffset = tk.utf16charsEnd() - 1 - delimiter.length();
- if (endDelimiterOffset <= delimiterOffset)
- return false;
- if (text.mid(endDelimiterOffset, delimiter.length()) != delimiter)
- return false;
- if (text.at(endDelimiterOffset - 1) != ')')
- return false;
+ // Conversely, since we are in a raw string literal that is not a continuation,
+ // the start sequence must be in here.
+ const int rOffset = text.indexOf(QLatin1String("R\""), tk.utf16charsBegin());
+ QTC_ASSERT(rOffset != -1, return);
+ const int tentativeDelimiterOffset = rOffset + 2;
+ const int openParenOffset = text.indexOf('(', tentativeDelimiterOffset);
+ QTC_ASSERT(openParenOffset != -1, return);
+ const QStringView delimiter = text.mid(tentativeDelimiterOffset,
+ openParenOffset - tentativeDelimiterOffset);
+ expectedSuffix = ')' + delimiter + '"';
+ delimiterOffset = tentativeDelimiterOffset;
+ stringOffset = delimiterOffset + delimiter.length() + 1;
+ stringLength -= delimiter.length() + 1;
+ }();
+ if (text.mid(tk.utf16charsBegin(), tk.utf16chars()).endsWith(expectedSuffix)) {
+ endDelimiterOffset = tk.utf16charsBegin() + tk.utf16chars() - expectedSuffix.size();
+ stringLength -= expectedSuffix.size();
+ }
// Step three: Do the actual formatting. For clarity, we display only the actual content as
// a string, and the rest (including the delimiter) as a keyword.
const QTextCharFormat delimiterFormat = formatForCategory(C_KEYWORD);
- const int stringOffset = delimiterOffset + delimiter.length() + 1;
- setFormat(tk.utf16charsBegin(), stringOffset, delimiterFormat);
- setFormatWithSpaces(text, stringOffset, endDelimiterOffset - stringOffset - 1,
- formatForCategory(C_STRING));
- setFormat(endDelimiterOffset - 1, delimiter.length() + 2, delimiterFormat);
+ if (delimiterOffset != -1)
+ setFormat(tk.utf16charsBegin(), stringOffset, delimiterFormat);
+ setFormatWithSpaces(text.toString(), stringOffset, stringLength, formatForCategory(C_STRING));
+ if (endDelimiterOffset != -1)
+ setFormat(endDelimiterOffset, expectedSuffix.size(), delimiterFormat);
return true;
}
@@ -434,3 +460,87 @@ void CppHighlighter::highlightDoxygenComment(const QString &text, int position,
setFormatWithSpaces(text, initial, it - uc - initial, format);
}
+namespace Internal {
+CppHighlighterTest::CppHighlighterTest()
+{
+ QFile source(":/cppeditor/testcases/highlightingtestcase.cpp");
+ QVERIFY(source.open(QIODevice::ReadOnly));
+
+ m_doc.setPlainText(QString::fromUtf8(source.readAll()));
+ setDocument(&m_doc);
+ rehighlight();
+}
+
+void CppHighlighterTest::test_data()
+{
+ QTest::addColumn<int>("line");
+ QTest::addColumn<int>("column");
+ QTest::addColumn<int>("lastLine");
+ QTest::addColumn<int>("lastColumn");
+ QTest::addColumn<TextStyle>("style");
+
+ QTest::newRow("auto") << 1 << 1 << 1 << 4 << C_KEYWORD;
+ QTest::newRow("opening brace") << 2 << 1 << 2 << 1 << C_PUNCTUATION;
+ QTest::newRow("return") << 3 << 5 << 3 << 10 << C_KEYWORD;
+ QTest::newRow("raw string prefix") << 3 << 12 << 3 << 14 << C_KEYWORD;
+ QTest::newRow("raw string content (multi-line)") << 3 << 15 << 6 << 13 << C_STRING;
+ QTest::newRow("raw string suffix") << 6 << 14 << 6 << 15 << C_KEYWORD;
+ QTest::newRow("raw string prefix 2") << 6 << 17 << 6 << 19 << C_KEYWORD;
+ QTest::newRow("raw string content 2") << 6 << 20 << 6 << 25 << C_STRING;
+ QTest::newRow("raw string suffix 2") << 6 << 26 << 6 << 27 << C_KEYWORD;
+ QTest::newRow("comment") << 6 << 29 << 6 << 41 << C_COMMENT;
+ QTest::newRow("raw string prefix 3") << 6 << 53 << 6 << 45 << C_KEYWORD;
+ QTest::newRow("raw string content 3") << 6 << 46 << 6 << 50 << C_STRING;
+ QTest::newRow("raw string suffix 3") << 6 << 51 << 6 << 52 << C_KEYWORD;
+ QTest::newRow("semicolon") << 6 << 53 << 6 << 53 << C_PUNCTUATION;
+ QTest::newRow("closing brace") << 7 << 1 << 7 << 1 << C_PUNCTUATION;
+}
+
+void CppHighlighterTest::test()
+{
+ QFETCH(int, line);
+ QFETCH(int, column);
+ QFETCH(int, lastLine);
+ QFETCH(int, lastColumn);
+ QFETCH(TextStyle, style);
+
+ const int startPos = Utils::Text::positionInText(&m_doc, line, column);
+ const int lastPos = Utils::Text::positionInText(&m_doc, lastLine, lastColumn);
+ const auto getActualFormat = [&](int pos) -> QTextCharFormat {
+ const QTextBlock block = m_doc.findBlock(pos);
+ if (!block.isValid())
+ return {};
+ const QList<QTextLayout::FormatRange> &ranges = block.layout()->formats();
+ for (const QTextLayout::FormatRange &range : ranges) {
+ const int offset = block.position() + range.start;
+ if (offset > pos)
+ return {};
+ if (offset + range.length <= pos)
+ continue;
+ return range.format;
+ }
+ return {};
+ };
+
+ const QTextCharFormat formatForStyle = formatForCategory(style);
+ for (int pos = startPos; pos <= lastPos; ++pos) {
+ const QChar c = m_doc.characterAt(pos);
+ if (c == QChar::ParagraphSeparator)
+ continue;
+ const QTextCharFormat expectedFormat = c.isSpace()
+ ? whitespacified(formatForStyle) : formatForStyle;
+ const QTextCharFormat actualFormat = getActualFormat(pos);
+ if (actualFormat != expectedFormat) {
+ int posLine;
+ int posCol;
+ Utils::Text::convertPosition(&m_doc, pos, &posLine, &posCol);
+ qDebug() << posLine << posCol << c
+ << actualFormat.foreground() << expectedFormat.foreground()
+ << actualFormat.background() << expectedFormat.background();
+ }
+ QCOMPARE(actualFormat, expectedFormat);
+ }
+}
+
+} // namespace Internal
+} // namespace CppEditor
diff --git a/src/plugins/cppeditor/cpphighlighter.h b/src/plugins/cppeditor/cpphighlighter.h
index 7e4451310ff..ee1f8251a87 100644
--- a/src/plugins/cppeditor/cpphighlighter.h
+++ b/src/plugins/cppeditor/cpphighlighter.h
@@ -25,7 +25,8 @@ public:
private:
void highlightWord(QStringView word, int position, int length);
- bool highlightRawStringLiteral(QStringView text, const CPlusPlus::Token &tk);
+ bool highlightRawStringLiteral(QStringView text, const CPlusPlus::Token &tk,
+ const QString &inheritedSuffix);
void highlightDoxygenComment(const QString &text, int position,
int length);
@@ -36,4 +37,21 @@ private:
CPlusPlus::LanguageFeatures m_languageFeatures = CPlusPlus::LanguageFeatures::defaultFeatures();
};
+namespace Internal {
+class CppHighlighterTest : public CppHighlighter
+{
+ Q_OBJECT
+
+public:
+ CppHighlighterTest();
+
+private slots:
+ void test_data();
+ void test();
+
+private:
+ QTextDocument m_doc;
+};
+} // namespace Internal
+
} // namespace CppEditor
diff --git a/src/plugins/cppeditor/semantichighlighter.cpp b/src/plugins/cppeditor/semantichighlighter.cpp
index 4c3c40967d9..473991787d0 100644
--- a/src/plugins/cppeditor/semantichighlighter.cpp
+++ b/src/plugins/cppeditor/semantichighlighter.cpp
@@ -27,69 +27,6 @@ namespace CppEditor {
static Utils::Id parenSource() { return "CppEditor"; }
-static const QList<std::pair<HighlightingResult, QTextBlock>>
-splitRawStringLiteral(const HighlightingResult &result, const QTextBlock &startBlock)
-{
- if (result.textStyles.mainStyle != C_STRING)
- return {{result, startBlock}};
-
- QTextCursor cursor(startBlock);
- cursor.setPosition(cursor.position() + result.column - 1);
- cursor.setPosition(cursor.position() + result.length, QTextCursor::KeepAnchor);
- const QString theString = cursor.selectedText();
-
- // Find all the components of a raw string literal. If we don't succeed, then it's
- // something else.
- if (!theString.endsWith('"'))
- return {{result, startBlock}};
- int rOffset = -1;
- if (theString.startsWith("R\"")) {
- rOffset = 0;
- } else if (theString.startsWith("LR\"")
- || theString.startsWith("uR\"")
- || theString.startsWith("UR\"")) {
- rOffset = 1;
- } else if (theString.startsWith("u8R\"")) {
- rOffset = 2;
- }
- if (rOffset == -1)
- return {{result, startBlock}};
- const int delimiterOffset = rOffset + 2;
- const int openParenOffset = theString.indexOf('(', delimiterOffset);
- if (openParenOffset == -1)
- return {{result, startBlock}};
- const QStringView delimiter = theString.mid(delimiterOffset, openParenOffset - delimiterOffset);
- const int endDelimiterOffset = theString.length() - 1 - delimiter.length();
- if (theString.mid(endDelimiterOffset, delimiter.length()) != delimiter)
- return {{result, startBlock}};
- if (theString.at(endDelimiterOffset - 1) != ')')
- return {{result, startBlock}};
-
- // Now split the result. For clarity, we display only the actual content as a string,
- // and the rest (including the delimiter) as a keyword.
- HighlightingResult prefix = result;
- prefix.textStyles.mainStyle = C_KEYWORD;
- prefix.textStyles.mixinStyles = {};
- prefix.length = delimiterOffset + delimiter.length() + 1;
- cursor.setPosition(startBlock.position() + result.column - 1 + prefix.length);
- QTextBlock stringBlock = cursor.block();
- HighlightingResult actualString = result;
- actualString.line = stringBlock.blockNumber() + 1;
- actualString.column = cursor.positionInBlock() + 1;
- actualString.length = endDelimiterOffset - openParenOffset - 2;
- cursor.setPosition(cursor.position() + actualString.length);
- QTextBlock suffixBlock = cursor.block();
- HighlightingResult suffix = result;
- suffix.textStyles.mainStyle = C_KEYWORD;
- suffix.textStyles.mixinStyles = {};
- suffix.line = suffixBlock.blockNumber() + 1;
- suffix.column = cursor.positionInBlock() + 1;
- suffix.length = delimiter.length() + 2;
- QTC_CHECK(prefix.length + actualString.length + suffix.length == result.length);
-
- return {{prefix, startBlock}, {actualString, stringBlock}, {suffix, suffixBlock}};
-}
-
SemanticHighlighter::SemanticHighlighter(TextDocument *baseTextDocument)
: QObject(baseTextDocument)
, m_baseTextDocument(baseTextDocument)
@@ -155,8 +92,7 @@ void SemanticHighlighter::onHighlighterResultAvailable(int from, int to)
SyntaxHighlighter *highlighter = m_baseTextDocument->syntaxHighlighter();
QTC_ASSERT(highlighter, return);
- incrementalApplyExtraAdditionalFormats(highlighter, m_watcher->future(), from, to, m_formatMap,
- &splitRawStringLiteral);
+ incrementalApplyExtraAdditionalFormats(highlighter, m_watcher->future(), from, to, m_formatMap);
// In addition to the paren matching that the syntactic highlighter does
// (parentheses, braces, brackets, comments), here we inject info from the code model
diff --git a/src/plugins/cppeditor/testcases/highlightingtestcase.cpp b/src/plugins/cppeditor/testcases/highlightingtestcase.cpp
new file mode 100644
index 00000000000..89d002e851e
--- /dev/null
+++ b/src/plugins/cppeditor/testcases/highlightingtestcase.cpp
@@ -0,0 +1,7 @@
+auto func()
+{
+ return R"(foo
+ foobar
+ R"notaprefix!(
+ barfoobar)" R"(second)" /* comment */ R"(third)";
+}
diff --git a/src/plugins/texteditor/syntaxhighlighter.cpp b/src/plugins/texteditor/syntaxhighlighter.cpp
index 4d12a697912..de72c7b0db6 100644
--- a/src/plugins/texteditor/syntaxhighlighter.cpp
+++ b/src/plugins/texteditor/syntaxhighlighter.cpp
@@ -478,8 +478,7 @@ void SyntaxHighlighter::setFormatWithSpaces(const QString &text, int start, int
const QTextCharFormat &format)
{
Q_D(const SyntaxHighlighter);
- QTextCharFormat visualSpaceFormat = d->whitespaceFormat;
- visualSpaceFormat.setBackground(format.background());
+ const QTextCharFormat visualSpaceFormat = whitespacified(format);
const int end = std::min(start + count, int(text.length()));
int index = start;
@@ -809,6 +808,14 @@ QTextCharFormat SyntaxHighlighter::formatForCategory(int category) const
return d->formats.at(category);
}
+QTextCharFormat SyntaxHighlighter::whitespacified(const QTextCharFormat &fmt)
+{
+ Q_D(SyntaxHighlighter);
+ QTextCharFormat format = d->whitespaceFormat;
+ format.setBackground(fmt.background());
+ return format;
+}
+
void SyntaxHighlighter::highlightBlock(const QString &text)
{
formatSpaces(text);
diff --git a/src/plugins/texteditor/syntaxhighlighter.h b/src/plugins/texteditor/syntaxhighlighter.h
index af02e955049..7a1822e4667 100644
--- a/src/plugins/texteditor/syntaxhighlighter.h
+++ b/src/plugins/texteditor/syntaxhighlighter.h
@@ -61,6 +61,7 @@ protected:
void setDefaultTextFormatCategories();
void setTextFormatCategories(int count, std::function<TextStyle(int)> formatMapping);
QTextCharFormat formatForCategory(int categoryIndex) const;
+ QTextCharFormat whitespacified(const QTextCharFormat &fmt);
// implement in subclasses
// default implementation highlights whitespace