fix syntax highlighting: use AbstractHighlighter directly

QSyntaxHighlighter applies colors to the layout only (not accessible
via QTextBlock iteration). Replace with a custom AbstractHighlighter
subclass (HtmlBuilder) that builds HTML directly in applyFormat().
This actually produces colored output.
This commit is contained in:
Levi Neely 2026-07-16 07:19:09 +02:00
parent 9f365f49b0
commit 5c093a3bb2
1 changed files with 46 additions and 36 deletions

View File

@ -4,8 +4,9 @@
#include <KSyntaxHighlighting/Repository>
#include <KSyntaxHighlighting/Definition>
#include <KSyntaxHighlighting/Theme>
#include <KSyntaxHighlighting/SyntaxHighlighter>
#include <QTextDocument>
#include <KSyntaxHighlighting/AbstractHighlighter>
#include <KSyntaxHighlighting/State>
#include <KSyntaxHighlighting/Format>
#include <QGuiApplication>
#include <QClipboard>
#include <QJsonDocument>
@ -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("<span style=\"color:") + fg.name() + QStringLiteral(";\">")
+ text + QStringLiteral("</span>");
} 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', "<br>");
QString plain = code.toHtmlEscaped().replace('\n', QStringLiteral("<br>"));
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("<span style=\"color:%1;\">%2</span>").arg(fg.name(), text);
} else {
html += text;
}
}
block = block.next();
if (block.isValid())
html += "<br>";
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("<br>");
}
m_highlightCache.insert(cacheKey, html);
return html;
m_highlightCache.insert(cacheKey, builder.html);
return builder.html;
}
QString ChatBlockModel::prettyJson(const QString &text) const