diff --git a/src/models/highlightedtext.cpp b/src/models/highlightedtext.cpp index 4ce9e6423..77e91cd2a 100644 --- a/src/models/highlightedtext.cpp +++ b/src/models/highlightedtext.cpp @@ -28,6 +28,7 @@ #include "formattingutils.h" #if KFSyntaxHighlighting_FOUND +// highlighter using KSyntaxHighlighting class HighlightingImplementation : public KSyntaxHighlighting::AbstractHighlighter { public: @@ -37,18 +38,11 @@ class HighlightingImplementation : public KSyntaxHighlighting::AbstractHighlight } ~HighlightingImplementation() override = default; - virtual QVector format(const QStringList& text) + virtual QVector format(const QString& text) { m_formats.clear(); - m_offset = 0; - KSyntaxHighlighting::State state; - for (const auto& line : text) { - state = highlightLine(line, state); - - // KSyntaxHighlighting uses line offsets but QTextLayout uses global offsets - m_offset += line.size(); - } + highlightLine(text, {}); return m_formats; } @@ -84,15 +78,16 @@ class HighlightingImplementation : public KSyntaxHighlighting::AbstractHighlight { QTextCharFormat textCharFormat; textCharFormat.setForeground(format.textColor(theme())); - m_formats.push_back({m_offset + offset, length, textCharFormat}); + textCharFormat.setFontWeight(format.isBold(theme()) ? QFont::Bold : QFont::Normal); + m_formats.push_back({offset, length, textCharFormat}); } private: KSyntaxHighlighting::Repository* m_repository; QVector m_formats; - int m_offset = 0; }; #else +// stub incase KSyntaxHighlighting is not available class HighlightingImplementation { public: @@ -122,7 +117,7 @@ class AnsiHighlightingImplementation : public HighlightingImplementation } ~AnsiHighlightingImplementation() override = default; - QVector format(const QStringList& text) final + QVector format(const QString& text) final { QVector formats; @@ -133,41 +128,39 @@ class AnsiHighlightingImplementation : public HighlightingImplementation constexpr int resetColorSequenceLength = 4; constexpr int colorCodeLength = 2; - for (const auto& line : text) { - auto lastToken = line.cbegin(); - int lineOffset = 0; - for (auto escapeIt = std::find(line.cbegin(), line.cend(), Util::escapeChar); escapeIt != line.cend(); - escapeIt = std::find(escapeIt, line.cend(), Util::escapeChar)) { - - lineOffset += std::distance(lastToken, escapeIt); - Q_ASSERT(*(escapeIt + 1) == QLatin1Char('[')); - - // escapeIt + 2 points to the first color code character - auto color = QStringView {escapeIt + 2, colorCodeLength}; - bool ok = false; - const uint8_t colorCode = color.toUInt(&ok); - if (ok) { - // only support the 8 default colors - Q_ASSERT(colorCode >= 30 && colorCode <= 37); - - format.start = offset + lineOffset; - const auto colorRole = static_cast(colorCode - 30); - format.format.setForeground(m_colorScheme.foreground(colorRole)); - - std::advance(escapeIt, setColorSequenceLength); - } else { - // make sure we have a reset sequence - Q_ASSERT(color == QStringLiteral("0m")); - format.length = offset + lineOffset - format.start; - if (format.length) { - formats.push_back(format); - } - - std::advance(escapeIt, resetColorSequenceLength); + auto lastToken = text.begin(); + for (auto escapeIt = std::find(text.cbegin(), text.cend(), Util::escapeChar); escapeIt != text.cend(); + escapeIt = std::find(escapeIt, text.cend(), Util::escapeChar)) { + + Q_ASSERT(*(escapeIt + 1) == QLatin1Char('[')); + + offset += std::distance(lastToken, escapeIt); + + // escapeIt + 2 points to the first color code character + auto color = QStringView {escapeIt + 2, colorCodeLength}; + bool ok = false; + const uint8_t colorCode = color.toUInt(&ok); + if (ok) { + // only support the 8 default colors + Q_ASSERT(colorCode >= 30 && colorCode <= 37); + + format.start = offset; + const auto colorRole = static_cast(colorCode - 30); + format.format.setForeground(m_colorScheme.foreground(colorRole)); + + std::advance(escapeIt, setColorSequenceLength); + } else { + // make sure we have a reset sequence + Q_ASSERT(color == QStringLiteral("0m")); + format.length = offset - format.start; + if (format.length) { + formats.push_back(format); } - lastToken = escapeIt; + + std::advance(escapeIt, resetColorSequenceLength); } - offset += lineOffset + std::distance(lastToken, line.cend()); + + lastToken = escapeIt; } return formats; @@ -188,15 +181,67 @@ class AnsiHighlightingImplementation : public HighlightingImplementation KColorScheme m_colorScheme; }; +// QTextLayout is slow, this class acts as a cache that only creates and fills the QTextLayout on demand +class HighlightedLine +{ +public: + HighlightedLine(HighlightingImplementation* highlighter, const QString& text) + : m_highlighter(highlighter) + , m_text(text) + , m_layout(nullptr) + { + } + + ~HighlightedLine() = default; + + HighlightedLine(HighlightedLine&&) = default; + + QTextLayout* layout() + { + if (!m_layout) { + doLayout(); + } + return m_layout.get(); + } + + void updateHighlighting() + { + m_layout = nullptr; + } + +private: + void doLayout() + { + if (!m_layout) { + m_layout = std::make_unique(); + m_layout->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); + const auto& ansiFreeLine = Util::removeAnsi(m_text); + m_layout->setText(ansiFreeLine); + } + + m_layout->setFormats(m_highlighter->format(m_text)); + + m_layout->beginLayout(); + + // there is at most one line, so we don't need to check this multiple times + QTextLine line = m_layout->createLine(); + if (line.isValid()) { + line.setPosition(QPointF(0, 0)); + } + m_layout->endLayout(); + } + + HighlightingImplementation* m_highlighter; + QString m_text; + std::unique_ptr m_layout; +}; + HighlightedText::HighlightedText(KSyntaxHighlighting::Repository* repository, QObject* parent) : QObject(parent) #if KFSyntaxHighlighting_FOUND , m_repository(repository) #endif - , m_layout(std::make_unique()) { - m_layout->setCacheEnabled(true); - m_layout->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont)); } HighlightedText::~HighlightedText() = default; @@ -212,29 +257,20 @@ void HighlightedText::setText(const QStringList& text) } else { m_highlighter = std::make_unique(m_repository); } + m_highlighter->themeChanged(); m_isUsingAnsi = usesAnsi; emit usesAnsiChanged(usesAnsi); } - m_highlighter->themeChanged(); - m_highlighter->format(text); - - m_lines.reserve(text.size()); - m_cleanedLines.reserve(text.size()); + m_highlightedLines.reserve(text.size()); + std::transform(text.cbegin(), text.cend(), std::back_inserter(m_highlightedLines), [this](const QString& text) { + return HighlightedLine {m_highlighter.get(), text}; + }); - QString formattedText; + connect(this, &HighlightedText::definitionChanged, this, &HighlightedText::updateHighlighting); - for (const auto& line : text) { - const auto& lineWithNewline = QLatin1String("%1%2").arg(line, QChar::LineSeparator); - const auto& ansiFreeLine = Util::removeAnsi(lineWithNewline); - m_cleanedLines.push_back(ansiFreeLine); - m_lines.push_back(lineWithNewline); - formattedText += ansiFreeLine; - } - - m_layout->setText(formattedText); - - applyFormatting(); + m_cleanedLines.reserve(text.size()); + std::transform(text.cbegin(), text.cend(), std::back_inserter(m_cleanedLines), Util::removeAnsi); } void HighlightedText::setDefinition(const KSyntaxHighlighting::Definition& definition) @@ -242,39 +278,18 @@ void HighlightedText::setDefinition(const KSyntaxHighlighting::Definition& defin Q_ASSERT(m_highlighter); m_highlighter->setHighlightingDefinition(definition); emit definitionChanged(definition.name()); - applyFormatting(); } QString HighlightedText::textAt(int index) const { Q_ASSERT(m_highlighter); - Q_ASSERT(index < m_cleanedLines.size()); return m_cleanedLines.at(index); } QTextLine HighlightedText::lineAt(int index) const { - Q_ASSERT(m_layout); - return m_layout->lineAt(index); -} - -void HighlightedText::applyFormatting() -{ - Q_ASSERT(m_highlighter); - - m_layout->setFormats(m_highlighter->format(m_lines)); - - m_layout->clearLayout(); - m_layout->beginLayout(); - - while (true) { - QTextLine line = m_layout->createLine(); - if (!line.isValid()) - break; - - line.setPosition(QPointF(0, 0)); - } - m_layout->endLayout(); + auto& line = m_highlightedLines[index]; + return line.layout()->lineAt(0); } QString HighlightedText::definition() const @@ -284,7 +299,14 @@ QString HighlightedText::definition() const return m_highlighter->definitionName(); } -QTextLayout* HighlightedText::layout() const +QTextLayout* HighlightedText::layoutForLine(int index) +{ + return m_highlightedLines[index].layout(); +} + +void HighlightedText::updateHighlighting() { - return m_layout.get(); + m_highlighter->themeChanged(); + std::for_each(m_highlightedLines.begin(), m_highlightedLines.end(), + [](HighlightedLine& line) { line.updateHighlighting(); }); } diff --git a/src/models/highlightedtext.h b/src/models/highlightedtext.h index 25110400d..88d2db06e 100644 --- a/src/models/highlightedtext.h +++ b/src/models/highlightedtext.h @@ -23,6 +23,7 @@ class Repository; } class HighlightingImplementation; +class HighlightedLine; class HighlightedText : public QObject { @@ -44,23 +45,21 @@ class HighlightedText : public QObject } // for testing - QTextLayout* layout() const; + QTextLayout* layoutForLine(int index); signals: void definitionChanged(const QString& definition); void usesAnsiChanged(bool usesAnsi); -private slots: - void applyFormatting(); - -private: +public slots: void updateHighlighting(); +private: #if KFSyntaxHighlighting_FOUND KSyntaxHighlighting::Repository* m_repository; #endif std::unique_ptr m_highlighter; - std::unique_ptr m_layout; + mutable std::vector m_highlightedLines; QStringList m_lines; QStringList m_cleanedLines; bool m_isUsingAnsi = false; diff --git a/src/resultsdisassemblypage.cpp b/src/resultsdisassemblypage.cpp index 556d92a39..a7922097b 100644 --- a/src/resultsdisassemblypage.cpp +++ b/src/resultsdisassemblypage.cpp @@ -620,4 +620,12 @@ void ResultsDisassemblyPage::setArch(const QString& arch) m_arch = arch.trimmed().toLower(); } +void ResultsDisassemblyPage::changeEvent(QEvent* event) +{ + if (event->type() == QEvent::PaletteChange) { + m_sourceCodeModel->highlightedText()->updateHighlighting(); + m_disassemblyModel->highlightedText()->updateHighlighting(); + } +} + #include "resultsdisassemblypage.moc" diff --git a/src/resultsdisassemblypage.h b/src/resultsdisassemblypage.h index 15e4c0901..4370d3302 100644 --- a/src/resultsdisassemblypage.h +++ b/src/resultsdisassemblypage.h @@ -57,6 +57,9 @@ class ResultsDisassemblyPage : public QWidget void navigateToCode(const QString& file, int lineNumber, int columnNumber); void stackChanged(); +protected: + void changeEvent(QEvent* event) override; + private: void setupAsmViewModel(); void showDisassembly(const DisassemblyOutput& disassemblyOutput); diff --git a/tests/modeltests/tst_formatting.cpp b/tests/modeltests/tst_formatting.cpp index cfd95ba00..cb2dba318 100644 --- a/tests/modeltests/tst_formatting.cpp +++ b/tests/modeltests/tst_formatting.cpp @@ -44,42 +44,43 @@ private slots: void testFormattingValidAnsiSequences_data() { QTest::addColumn("ansiStrings"); - QTest::addColumn>("formatting"); + QTest::addColumn>>("formatting"); - QTest::addRow("no ansi sequence") << QStringList {QStringLiteral(" A B C D E ")} - << QVector {{0, 16, {}}}; // only default formatting + QTest::addRow("no ansi sequence") + << QStringList {QStringLiteral(" A B C D E ")} + << QVector> {{{0, 15, {}}}}; // only default formatting QTest::addRow("one ansi sequence") << QStringList {QStringLiteral("\u001B[33mHello World\u001B[0m")} - << QVector {{0, 11, {}}}; + << QVector> {{{0, 11, {}}}}; QTest::addRow("two ansi sequences") << QStringList {QStringLiteral("\u001B[33mHello\u001B[0m \u001B[31mWorld\u001B[0m")} - << QVector {{0, 5, {}}, {6, 5, {}}}; + << QVector> {{{0, 5, {}}, {6, 5, {}}}}; QTest::addRow("two ansi lines") << QStringList {QStringLiteral("\u001B[33mHello\u001B[0m\n"), QStringLiteral("\u001B[31mWorld\u001B[0m")} - << QVector {{0, 5, {}}, {7, 5, {}}}; - - QTest::addRow("two ansi sequences without break") - << QStringList {QStringLiteral("\u001B[33m\u001B[0mhello\u001B[33m\u001B[0m")} - << QVector {}; + << QVector> {{{0, 5, {}}}, {{0, 5, {}}}}; } void testFormattingValidAnsiSequences() { QFETCH(QStringList, ansiStrings); - QFETCH(QVector, formatting); + QFETCH(QVector>, formatting); HighlightedText highlighter(nullptr); highlighter.setText(ansiStrings); - auto layout = highlighter.layout(); - QVERIFY(layout); - auto format = layout->formats(); - QCOMPARE(format.size(), formatting.size()); + for (int ansiStringIndex = 0; ansiStringIndex < ansiStrings.count(); ansiStringIndex++) { + auto layout = highlighter.layoutForLine(ansiStringIndex); + QVERIFY(layout); + auto format = layout->formats(); + + QCOMPARE(format.size(), formatting[ansiStringIndex].size()); - for (int i = 0; i < format.size(); i++) { - QCOMPARE(format[i].start, formatting[i].start); - QCOMPARE(format[i].length, formatting[i].length); + for (int i = 0; i < format.size(); i++) { + auto& formattingLine = formatting[ansiStringIndex]; + QCOMPARE(format[i].start, formattingLine[i].start); + QCOMPARE(format[i].length, formattingLine[i].length); + } } } };