28
28
#include " formattingutils.h"
29
29
30
30
#if KFSyntaxHighlighting_FOUND
31
+ // highlighter using KSyntaxHighlighting
31
32
class HighlightingImplementation : public KSyntaxHighlighting ::AbstractHighlighter
32
33
{
33
34
public:
@@ -37,18 +38,11 @@ class HighlightingImplementation : public KSyntaxHighlighting::AbstractHighlight
37
38
}
38
39
~HighlightingImplementation () override = default ;
39
40
40
- virtual QVector<QTextLayout::FormatRange> format (const QStringList & text)
41
+ virtual QVector<QTextLayout::FormatRange> format (const QString & text)
41
42
{
42
43
m_formats.clear ();
43
- m_offset = 0 ;
44
44
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, {});
52
46
53
47
return m_formats;
54
48
}
@@ -84,15 +78,16 @@ class HighlightingImplementation : public KSyntaxHighlighting::AbstractHighlight
84
78
{
85
79
QTextCharFormat textCharFormat;
86
80
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});
88
83
}
89
84
90
85
private:
91
86
KSyntaxHighlighting::Repository* m_repository;
92
87
QVector<QTextLayout::FormatRange> m_formats;
93
- int m_offset = 0 ;
94
88
};
95
89
#else
90
+ // stub incase KSyntaxHighlighting is not available
96
91
class HighlightingImplementation
97
92
{
98
93
public:
@@ -122,7 +117,7 @@ class AnsiHighlightingImplementation : public HighlightingImplementation
122
117
}
123
118
~AnsiHighlightingImplementation () override = default ;
124
119
125
- QVector<QTextLayout::FormatRange> format (const QStringList & text) final
120
+ QVector<QTextLayout::FormatRange> format (const QString & text) final
126
121
{
127
122
QVector<QTextLayout::FormatRange> formats;
128
123
@@ -133,41 +128,39 @@ class AnsiHighlightingImplementation : public HighlightingImplementation
133
128
constexpr int resetColorSequenceLength = 4 ;
134
129
constexpr int colorCodeLength = 2 ;
135
130
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);
167
158
}
168
- lastToken = escapeIt;
159
+
160
+ std::advance (escapeIt, resetColorSequenceLength);
169
161
}
170
- offset += lineOffset + std::distance (lastToken, line.cend ());
162
+
163
+ lastToken = escapeIt;
171
164
}
172
165
173
166
return formats;
@@ -188,15 +181,67 @@ class AnsiHighlightingImplementation : public HighlightingImplementation
188
181
KColorScheme m_colorScheme;
189
182
};
190
183
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
+
191
239
HighlightedText::HighlightedText (KSyntaxHighlighting::Repository* repository, QObject* parent)
192
240
: QObject(parent)
193
241
#if KFSyntaxHighlighting_FOUND
194
242
, m_repository(repository)
195
243
#endif
196
- , m_layout(std::make_unique<QTextLayout>())
197
244
{
198
- m_layout->setCacheEnabled (true );
199
- m_layout->setFont (QFontDatabase::systemFont (QFontDatabase::FixedFont));
200
245
}
201
246
202
247
HighlightedText::~HighlightedText () = default ;
@@ -212,69 +257,39 @@ void HighlightedText::setText(const QStringList& text)
212
257
} else {
213
258
m_highlighter = std::make_unique<HighlightingImplementation>(m_repository);
214
259
}
260
+ m_highlighter->themeChanged ();
215
261
m_isUsingAnsi = usesAnsi;
216
262
emit usesAnsiChanged (usesAnsi);
217
263
}
218
264
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
+ });
224
269
225
- QString formattedText ;
270
+ connect ( this , &HighlightedText::definitionChanged, this , &HighlightedText::updateHighlighting) ;
226
271
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);
238
274
}
239
275
240
276
void HighlightedText::setDefinition (const KSyntaxHighlighting::Definition& definition)
241
277
{
242
278
Q_ASSERT (m_highlighter);
243
279
m_highlighter->setHighlightingDefinition (definition);
244
280
emit definitionChanged (definition.name ());
245
- applyFormatting ();
246
281
}
247
282
248
283
QString HighlightedText::textAt (int index) const
249
284
{
250
285
Q_ASSERT (m_highlighter);
251
- Q_ASSERT (index < m_cleanedLines.size ());
252
286
return m_cleanedLines.at (index );
253
287
}
254
288
255
289
QTextLine HighlightedText::lineAt (int index) const
256
290
{
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 );
278
293
}
279
294
280
295
QString HighlightedText::definition () const
@@ -284,7 +299,14 @@ QString HighlightedText::definition() const
284
299
return m_highlighter->definitionName ();
285
300
}
286
301
287
- QTextLayout* HighlightedText::layout () const
302
+ QTextLayout* HighlightedText::layoutForLine (int index)
303
+ {
304
+ return m_highlightedLines[index ].layout ();
305
+ }
306
+
307
+ void HighlightedText::updateHighlighting ()
288
308
{
289
- return m_layout.get ();
309
+ m_highlighter->themeChanged ();
310
+ std::for_each (m_highlightedLines.begin (), m_highlightedLines.end (),
311
+ [](HighlightedLine& line) { line.updateHighlighting (); });
290
312
}
0 commit comments