diff --git a/gui/chatblockmodel.cpp b/gui/chatblockmodel.cpp index 2908651..18a3789 100644 --- a/gui/chatblockmodel.cpp +++ b/gui/chatblockmodel.cpp @@ -4,8 +4,9 @@ #include #include #include -#include -#include +#include +#include +#include #include #include #include @@ -329,6 +330,37 @@ QVariantList ChatBlockModel::parseSubBlocks(const QString &content) const return result; } +// Custom highlighter that builds HTML string directly +class HtmlBuilder : public KSyntaxHighlighting::AbstractHighlighter +{ +public: + QString html; + + using AbstractHighlighter::AbstractHighlighter; + + KSyntaxHighlighting::State processLine(const QString &line, const KSyntaxHighlighting::State &state) + { + m_currentLine = line; + return highlightLine(line, state); + } + +protected: + void applyFormat(int offset, int length, const KSyntaxHighlighting::Format &format) override + { + QString text = m_currentLine.mid(offset, length).toHtmlEscaped(); + if (format.hasTextColor(theme())) { + QColor fg = format.textColor(theme()); + html += QStringLiteral("") + + text + QStringLiteral(""); + } else { + html += text; + } + } + +private: + QString m_currentLine; +}; + QString ChatBlockModel::highlightedHtml(const QString &code, const QString &language) const { // Check cache first @@ -339,55 +371,33 @@ QString ChatBlockModel::highlightedHtml(const QString &code, const QString &lang auto def = repo().definitionForName(language); if (!def.isValid()) { - // Try by file extension def = repo().definitionForFileName("file." + language); } if (!def.isValid()) { - // Return escaped plain text - QString plain = code.toHtmlEscaped().replace('\n', "
"); + QString plain = code.toHtmlEscaped().replace('\n', QStringLiteral("
")); m_highlightCache.insert(cacheKey, plain); return plain; } - // Determine theme based on current palette bool dark = QGuiApplication::palette().color(QPalette::Window).lightnessF() < 0.5; auto theme = dark ? repo().defaultTheme(KSyntaxHighlighting::Repository::DarkTheme) : repo().defaultTheme(KSyntaxHighlighting::Repository::LightTheme); - QTextDocument doc; - doc.setPlainText(code); + HtmlBuilder builder; + builder.setDefinition(def); + builder.setTheme(theme); - auto *highlighter = new KSyntaxHighlighting::SyntaxHighlighter(&doc); - highlighter->setDefinition(def); - highlighter->setTheme(theme); - highlighter->rehighlight(); - - // Extract HTML from the highlighted document - QString html; - QTextBlock block = doc.begin(); - while (block.isValid()) { - for (auto it = block.begin(); !it.atEnd(); ++it) { - QTextFragment fragment = it.fragment(); - if (!fragment.isValid()) continue; - - QTextCharFormat fmt = fragment.charFormat(); - QString text = fragment.text().toHtmlEscaped(); - - if (fmt.foreground().style() != Qt::NoBrush) { - QColor fg = fmt.foreground().color(); - html += QString("%2").arg(fg.name(), text); - } else { - html += text; - } - } - block = block.next(); - if (block.isValid()) - html += "
"; + KSyntaxHighlighting::State state; + const QStringList lines = code.split('\n'); + for (int i = 0; i < lines.size(); ++i) { + state = builder.processLine(lines[i], state); + if (i < lines.size() - 1) + builder.html += QStringLiteral("
"); } - m_highlightCache.insert(cacheKey, html); - return html; + m_highlightCache.insert(cacheKey, builder.html); + return builder.html; } QString ChatBlockModel::prettyJson(const QString &text) const