Skip to content

Commit 5445957

Browse files
committed
use cache for layout since QTextLayout::setFormats is slow
QTextLayout::setFormats is really slow so this patch introduces a cache named HighlightedLine which will only call QTextLayout::setFormats if necessary
1 parent 1f1691c commit 5445957

File tree

5 files changed

+147
-114
lines changed

5 files changed

+147
-114
lines changed

src/models/highlightedtext.cpp

+112-90
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
#include "formattingutils.h"
2929

3030
#if KFSyntaxHighlighting_FOUND
31+
// highlighter using KSyntaxHighlighting
3132
class HighlightingImplementation : public KSyntaxHighlighting::AbstractHighlighter
3233
{
3334
public:
@@ -37,18 +38,11 @@ class HighlightingImplementation : public KSyntaxHighlighting::AbstractHighlight
3738
}
3839
~HighlightingImplementation() override = default;
3940

40-
virtual QVector<QTextLayout::FormatRange> format(const QStringList& text)
41+
virtual QVector<QTextLayout::FormatRange> format(const QString& text)
4142
{
4243
m_formats.clear();
43-
m_offset = 0;
4444

45-
KSyntaxHighlighting::State state;
46-
for (const auto& line : text) {
47-
state = highlightLine(line, state);
48-
49-
// KSyntaxHighlighting uses line offsets but QTextLayout uses global offsets
50-
m_offset += line.size();
51-
}
45+
highlightLine(text, {});
5246

5347
return m_formats;
5448
}
@@ -84,15 +78,16 @@ class HighlightingImplementation : public KSyntaxHighlighting::AbstractHighlight
8478
{
8579
QTextCharFormat textCharFormat;
8680
textCharFormat.setForeground(format.textColor(theme()));
87-
m_formats.push_back({m_offset + offset, length, textCharFormat});
81+
textCharFormat.setFontWeight(format.isBold(theme()) ? QFont::Bold : QFont::Normal);
82+
m_formats.push_back({offset, length, textCharFormat});
8883
}
8984

9085
private:
9186
KSyntaxHighlighting::Repository* m_repository;
9287
QVector<QTextLayout::FormatRange> m_formats;
93-
int m_offset = 0;
9488
};
9589
#else
90+
// stub incase KSyntaxHighlighting is not available
9691
class HighlightingImplementation
9792
{
9893
public:
@@ -122,7 +117,7 @@ class AnsiHighlightingImplementation : public HighlightingImplementation
122117
}
123118
~AnsiHighlightingImplementation() override = default;
124119

125-
QVector<QTextLayout::FormatRange> format(const QStringList& text) final
120+
QVector<QTextLayout::FormatRange> format(const QString& text) final
126121
{
127122
QVector<QTextLayout::FormatRange> formats;
128123

@@ -133,41 +128,39 @@ class AnsiHighlightingImplementation : public HighlightingImplementation
133128
constexpr int resetColorSequenceLength = 4;
134129
constexpr int colorCodeLength = 2;
135130

136-
for (const auto& line : text) {
137-
auto lastToken = line.cbegin();
138-
int lineOffset = 0;
139-
for (auto escapeIt = std::find(line.cbegin(), line.cend(), Util::escapeChar); escapeIt != line.cend();
140-
escapeIt = std::find(escapeIt, line.cend(), Util::escapeChar)) {
141-
142-
lineOffset += std::distance(lastToken, escapeIt);
143-
Q_ASSERT(*(escapeIt + 1) == QLatin1Char('['));
144-
145-
// escapeIt + 2 points to the first color code character
146-
auto color = QStringView {escapeIt + 2, colorCodeLength};
147-
bool ok = false;
148-
const uint8_t colorCode = color.toUInt(&ok);
149-
if (ok) {
150-
// only support the 8 default colors
151-
Q_ASSERT(colorCode >= 30 && colorCode <= 37);
152-
153-
format.start = offset + lineOffset;
154-
const auto colorRole = static_cast<KColorScheme::ForegroundRole>(colorCode - 30);
155-
format.format.setForeground(m_colorScheme.foreground(colorRole));
156-
157-
std::advance(escapeIt, setColorSequenceLength);
158-
} else {
159-
// make sure we have a reset sequence
160-
Q_ASSERT(color == QStringLiteral("0m"));
161-
format.length = offset + lineOffset - format.start;
162-
if (format.length) {
163-
formats.push_back(format);
164-
}
165-
166-
std::advance(escapeIt, resetColorSequenceLength);
131+
auto lastToken = text.begin();
132+
for (auto escapeIt = std::find(text.cbegin(), text.cend(), Util::escapeChar); escapeIt != text.cend();
133+
escapeIt = std::find(escapeIt, text.cend(), Util::escapeChar)) {
134+
135+
Q_ASSERT(*(escapeIt + 1) == QLatin1Char('['));
136+
137+
offset += std::distance(lastToken, escapeIt);
138+
139+
// escapeIt + 2 points to the first color code character
140+
auto color = QStringView {escapeIt + 2, colorCodeLength};
141+
bool ok = false;
142+
const uint8_t colorCode = color.toUInt(&ok);
143+
if (ok) {
144+
// only support the 8 default colors
145+
Q_ASSERT(colorCode >= 30 && colorCode <= 37);
146+
147+
format.start = offset;
148+
const auto colorRole = static_cast<KColorScheme::ForegroundRole>(colorCode - 30);
149+
format.format.setForeground(m_colorScheme.foreground(colorRole));
150+
151+
std::advance(escapeIt, setColorSequenceLength);
152+
} else {
153+
// make sure we have a reset sequence
154+
Q_ASSERT(color == QStringLiteral("0m"));
155+
format.length = offset - format.start;
156+
if (format.length) {
157+
formats.push_back(format);
167158
}
168-
lastToken = escapeIt;
159+
160+
std::advance(escapeIt, resetColorSequenceLength);
169161
}
170-
offset += lineOffset + std::distance(lastToken, line.cend());
162+
163+
lastToken = escapeIt;
171164
}
172165

173166
return formats;
@@ -188,15 +181,67 @@ class AnsiHighlightingImplementation : public HighlightingImplementation
188181
KColorScheme m_colorScheme;
189182
};
190183

184+
// QTextLayout is slow, this class acts as a cache that only creates and fills the QTextLayout on demand
185+
class HighlightedLine
186+
{
187+
public:
188+
HighlightedLine(HighlightingImplementation* highlighter, const QString& text)
189+
: m_highlighter(highlighter)
190+
, m_text(text)
191+
, m_layout(nullptr)
192+
{
193+
}
194+
195+
~HighlightedLine() = default;
196+
197+
HighlightedLine(HighlightedLine&&) = default;
198+
199+
QTextLayout* layout()
200+
{
201+
if (!m_layout) {
202+
doLayout();
203+
}
204+
return m_layout.get();
205+
}
206+
207+
void updateHighlighting()
208+
{
209+
m_layout = nullptr;
210+
}
211+
212+
private:
213+
void doLayout()
214+
{
215+
if (!m_layout) {
216+
m_layout = std::make_unique<QTextLayout>();
217+
m_layout->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
218+
const auto& ansiFreeLine = Util::removeAnsi(m_text);
219+
m_layout->setText(ansiFreeLine);
220+
}
221+
222+
m_layout->setFormats(m_highlighter->format(m_text));
223+
224+
m_layout->beginLayout();
225+
226+
// there is at most one line, so we don't need to check this multiple times
227+
QTextLine line = m_layout->createLine();
228+
if (line.isValid()) {
229+
line.setPosition(QPointF(0, 0));
230+
}
231+
m_layout->endLayout();
232+
}
233+
234+
HighlightingImplementation* m_highlighter;
235+
QString m_text;
236+
std::unique_ptr<QTextLayout> m_layout;
237+
};
238+
191239
HighlightedText::HighlightedText(KSyntaxHighlighting::Repository* repository, QObject* parent)
192240
: QObject(parent)
193241
#if KFSyntaxHighlighting_FOUND
194242
, m_repository(repository)
195243
#endif
196-
, m_layout(std::make_unique<QTextLayout>())
197244
{
198-
m_layout->setCacheEnabled(true);
199-
m_layout->setFont(QFontDatabase::systemFont(QFontDatabase::FixedFont));
200245
}
201246

202247
HighlightedText::~HighlightedText() = default;
@@ -212,69 +257,39 @@ void HighlightedText::setText(const QStringList& text)
212257
} else {
213258
m_highlighter = std::make_unique<HighlightingImplementation>(m_repository);
214259
}
260+
m_highlighter->themeChanged();
215261
m_isUsingAnsi = usesAnsi;
216262
emit usesAnsiChanged(usesAnsi);
217263
}
218264

219-
m_highlighter->themeChanged();
220-
m_highlighter->format(text);
221-
222-
m_lines.reserve(text.size());
223-
m_cleanedLines.reserve(text.size());
265+
m_highlightedLines.reserve(text.size());
266+
std::transform(text.cbegin(), text.cend(), std::back_inserter(m_highlightedLines), [this](const QString& text) {
267+
return HighlightedLine {m_highlighter.get(), text};
268+
});
224269

225-
QString formattedText;
270+
connect(this, &HighlightedText::definitionChanged, this, &HighlightedText::updateHighlighting);
226271

227-
for (const auto& line : text) {
228-
const auto& lineWithNewline = QLatin1String("%1%2").arg(line, QChar::LineSeparator);
229-
const auto& ansiFreeLine = Util::removeAnsi(lineWithNewline);
230-
m_cleanedLines.push_back(ansiFreeLine);
231-
m_lines.push_back(lineWithNewline);
232-
formattedText += ansiFreeLine;
233-
}
234-
235-
m_layout->setText(formattedText);
236-
237-
applyFormatting();
272+
m_cleanedLines.reserve(text.size());
273+
std::transform(text.cbegin(), text.cend(), std::back_inserter(m_cleanedLines), Util::removeAnsi);
238274
}
239275

240276
void HighlightedText::setDefinition(const KSyntaxHighlighting::Definition& definition)
241277
{
242278
Q_ASSERT(m_highlighter);
243279
m_highlighter->setHighlightingDefinition(definition);
244280
emit definitionChanged(definition.name());
245-
applyFormatting();
246281
}
247282

248283
QString HighlightedText::textAt(int index) const
249284
{
250285
Q_ASSERT(m_highlighter);
251-
Q_ASSERT(index < m_cleanedLines.size());
252286
return m_cleanedLines.at(index);
253287
}
254288

255289
QTextLine HighlightedText::lineAt(int index) const
256290
{
257-
Q_ASSERT(m_layout);
258-
return m_layout->lineAt(index);
259-
}
260-
261-
void HighlightedText::applyFormatting()
262-
{
263-
Q_ASSERT(m_highlighter);
264-
265-
m_layout->setFormats(m_highlighter->format(m_lines));
266-
267-
m_layout->clearLayout();
268-
m_layout->beginLayout();
269-
270-
while (true) {
271-
QTextLine line = m_layout->createLine();
272-
if (!line.isValid())
273-
break;
274-
275-
line.setPosition(QPointF(0, 0));
276-
}
277-
m_layout->endLayout();
291+
auto& line = m_highlightedLines[index];
292+
return line.layout()->lineAt(0);
278293
}
279294

280295
QString HighlightedText::definition() const
@@ -284,7 +299,14 @@ QString HighlightedText::definition() const
284299
return m_highlighter->definitionName();
285300
}
286301

287-
QTextLayout* HighlightedText::layout() const
302+
QTextLayout* HighlightedText::layoutForLine(int index)
303+
{
304+
return m_highlightedLines[index].layout();
305+
}
306+
307+
void HighlightedText::updateHighlighting()
288308
{
289-
return m_layout.get();
309+
m_highlighter->themeChanged();
310+
std::for_each(m_highlightedLines.begin(), m_highlightedLines.end(),
311+
[](HighlightedLine& line) { line.updateHighlighting(); });
290312
}

src/models/highlightedtext.h

+5-6
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class Repository;
2323
}
2424

2525
class HighlightingImplementation;
26+
class HighlightedLine;
2627

2728
class HighlightedText : public QObject
2829
{
@@ -44,23 +45,21 @@ class HighlightedText : public QObject
4445
}
4546

4647
// for testing
47-
QTextLayout* layout() const;
48+
QTextLayout* layoutForLine(int index);
4849

4950
signals:
5051
void definitionChanged(const QString& definition);
5152
void usesAnsiChanged(bool usesAnsi);
5253

53-
private slots:
54-
void applyFormatting();
55-
56-
private:
54+
public slots:
5755
void updateHighlighting();
5856

57+
private:
5958
#if KFSyntaxHighlighting_FOUND
6059
KSyntaxHighlighting::Repository* m_repository;
6160
#endif
6261
std::unique_ptr<HighlightingImplementation> m_highlighter;
63-
std::unique_ptr<QTextLayout> m_layout;
62+
mutable std::vector<HighlightedLine> m_highlightedLines;
6463
QStringList m_lines;
6564
QStringList m_cleanedLines;
6665
bool m_isUsingAnsi = false;

src/resultsdisassemblypage.cpp

+8
Original file line numberDiff line numberDiff line change
@@ -614,4 +614,12 @@ void ResultsDisassemblyPage::setArch(const QString& arch)
614614
m_arch = arch.trimmed().toLower();
615615
}
616616

617+
void ResultsDisassemblyPage::changeEvent(QEvent* event)
618+
{
619+
if (event->type() == QEvent::PaletteChange) {
620+
m_sourceCodeModel->highlightedText()->updateHighlighting();
621+
m_disassemblyModel->highlightedText()->updateHighlighting();
622+
}
623+
}
624+
617625
#include "resultsdisassemblypage.moc"

src/resultsdisassemblypage.h

+3
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ class ResultsDisassemblyPage : public QWidget
5757
void navigateToCode(const QString& file, int lineNumber, int columnNumber);
5858
void stackChanged();
5959

60+
protected:
61+
void changeEvent(QEvent* event) override;
62+
6063
private:
6164
void setupAsmViewModel();
6265
void showDisassembly(const DisassemblyOutput& disassemblyOutput);

0 commit comments

Comments
 (0)