1/****************************************************************************
2**
3** Copyright (C) 2016 The Qt Company Ltd.
4** Contact: https://www.qt.io/licensing/
5**
6** This file is part of the test suite of the Qt Toolkit.
7**
8** $QT_BEGIN_LICENSE:GPL-EXCEPT$
9** Commercial License Usage
10** Licensees holding valid commercial Qt licenses may use this file in
11** accordance with the commercial license agreement provided with the
12** Software or, alternatively, in accordance with the terms contained in
13** a written agreement between you and The Qt Company. For licensing terms
14** and conditions see https://www.qt.io/terms-conditions. For further
15** information use the contact form at https://www.qt.io/contact-us.
16**
17** GNU General Public License Usage
18** Alternatively, this file may be used under the terms of the GNU
19** General Public License version 3 as published by the Free Software
20** Foundation with exceptions as appearing in the file LICENSE.GPL3-EXCEPT
21** included in the packaging of this file. Please review the following
22** information to ensure the GNU General Public License requirements will
23** be met: https://www.gnu.org/licenses/gpl-3.0.html.
24**
25** $QT_END_LICENSE$
26**
27****************************************************************************/
28
29
30#include <QtTest/QtTest>
31#include <QTextDocument>
32#include <QTextLayout>
33#include <QDebug>
34#include <QAbstractTextDocumentLayout>
35#include <QSyntaxHighlighter>
36
37#ifndef QT_NO_WIDGETS
38#include <QTextEdit>
39#endif
40
41class QTestDocumentLayout : public QAbstractTextDocumentLayout
42{
43 Q_OBJECT
44public:
45 inline QTestDocumentLayout(QTextDocument *doc)
46 : QAbstractTextDocumentLayout(doc), documentChangedCalled(false) {}
47
48 virtual void draw(QPainter *, const QAbstractTextDocumentLayout::PaintContext &) {}
49
50 virtual int hitTest(const QPointF &, Qt::HitTestAccuracy ) const { return 0; }
51
52 virtual void documentChanged(int, int, int) { documentChangedCalled = true; }
53
54 virtual int pageCount() const { return 1; }
55
56 virtual QSizeF documentSize() const { return QSize(); }
57
58 virtual QRectF frameBoundingRect(QTextFrame *) const { return QRectF(); }
59 virtual QRectF blockBoundingRect(const QTextBlock &) const { return QRectF(); }
60
61 bool documentChangedCalled;
62};
63
64class tst_QSyntaxHighlighter : public QObject
65{
66 Q_OBJECT
67
68private slots:
69 void init();
70 void cleanup();
71 void basic();
72 void basicTwo();
73 void removeFormatsOnDelete();
74 void emptyBlocks();
75 void setCharFormat();
76 void highlightOnInit();
77 void highlightOnInitAndAppend();
78 void stopHighlightingWhenStateDoesNotChange();
79 void unindent();
80 void highlightToEndOfDocument();
81 void highlightToEndOfDocument2();
82 void preservePreeditArea();
83 void task108530();
84 void avoidUnnecessaryRehighlight();
85 void avoidUnnecessaryDelayedRehighlight();
86 void noContentsChangedDuringHighlight();
87 void rehighlight();
88 void rehighlightBlock();
89#ifndef QT_NO_WIDGETS
90 void textEditParent();
91#endif
92
93private:
94 QTextDocument *doc;
95 QTestDocumentLayout *lout;
96 QTextCursor cursor;
97};
98
99void tst_QSyntaxHighlighter::init()
100{
101 doc = new QTextDocument;
102 lout = new QTestDocumentLayout(doc);
103 doc->setDocumentLayout(lout);
104 cursor = QTextCursor(doc);
105}
106
107void tst_QSyntaxHighlighter::cleanup()
108{
109 delete doc;
110 doc = 0;
111}
112
113class TestHighlighter : public QSyntaxHighlighter
114{
115public:
116 inline TestHighlighter(const QVector<QTextLayout::FormatRange> &fmts, QTextDocument *parent)
117 : QSyntaxHighlighter(parent), formats(fmts), highlighted(false), callCount(0) {}
118 inline TestHighlighter(QObject *parent)
119 : QSyntaxHighlighter(parent) {}
120 inline TestHighlighter(QTextDocument *parent)
121 : QSyntaxHighlighter(parent), highlighted(false), callCount(0) {}
122
123 virtual void highlightBlock(const QString &text)
124 {
125 for (int i = 0; i < formats.count(); ++i) {
126 const QTextLayout::FormatRange &range = formats.at(i);
127 setFormat(start: range.start, count: range.length, format: range.format);
128 }
129 highlighted = true;
130 highlightedText += text;
131 ++callCount;
132 }
133
134 QVector<QTextLayout::FormatRange> formats;
135 bool highlighted;
136 int callCount;
137 QString highlightedText;
138};
139
140void tst_QSyntaxHighlighter::basic()
141{
142 QVector<QTextLayout::FormatRange> formats;
143 QTextLayout::FormatRange range;
144 range.start = 0;
145 range.length = 2;
146 range.format.setForeground(Qt::blue);
147 formats.append(t: range);
148
149 range.start = 4;
150 range.length = 2;
151 range.format.setFontItalic(true);
152 formats.append(t: range);
153
154 range.start = 9;
155 range.length = 2;
156 range.format.setFontUnderline(true);
157 formats.append(t: range);
158
159 TestHighlighter *hl = new TestHighlighter(formats, doc);
160
161 lout->documentChangedCalled = false;
162 doc->setPlainText("Hello World");
163 QVERIFY(hl->highlighted);
164 QVERIFY(lout->documentChangedCalled);
165
166 QCOMPARE(doc->begin().layout()->formats(), formats);
167}
168
169class CommentTestHighlighter : public QSyntaxHighlighter
170{
171public:
172 inline CommentTestHighlighter(QTextDocument *parent)
173 : QSyntaxHighlighter(parent), highlighted(false) {}
174
175 inline void reset()
176 {
177 highlighted = false;
178 }
179
180 virtual void highlightBlock(const QString &text)
181 {
182 QTextCharFormat commentFormat;
183 commentFormat.setForeground(Qt::darkGreen);
184 commentFormat.setFontWeight(QFont::StyleItalic);
185 commentFormat.setFontFixedPitch(true);
186 int textLength = text.length();
187
188 if (text.startsWith(c: QLatin1Char(';'))){
189 // The entire line is a comment
190 setFormat(start: 0, count: textLength, format: commentFormat);
191 highlighted = true;
192 }
193 }
194 bool highlighted;
195};
196
197
198void tst_QSyntaxHighlighter::basicTwo()
199{
200 // Done for task 104409
201 CommentTestHighlighter *hl = new CommentTestHighlighter(doc);
202 doc->setPlainText("; a test");
203 QVERIFY(hl->highlighted);
204 QVERIFY(lout->documentChangedCalled);
205}
206
207void tst_QSyntaxHighlighter::removeFormatsOnDelete()
208{
209 QVector<QTextLayout::FormatRange> formats;
210 QTextLayout::FormatRange range;
211 range.start = 0;
212 range.length = 9;
213 range.format.setForeground(Qt::blue);
214 formats.append(t: range);
215
216 TestHighlighter *hl = new TestHighlighter(formats, doc);
217
218 lout->documentChangedCalled = false;
219 doc->setPlainText("Hello World");
220 QVERIFY(hl->highlighted);
221 QVERIFY(lout->documentChangedCalled);
222
223 lout->documentChangedCalled = false;
224 QVERIFY(!doc->begin().layout()->formats().isEmpty());
225 delete hl;
226 QVERIFY(doc->begin().layout()->formats().isEmpty());
227 QVERIFY(lout->documentChangedCalled);
228}
229
230void tst_QSyntaxHighlighter::emptyBlocks()
231{
232 TestHighlighter *hl = new TestHighlighter(doc);
233
234 cursor.insertText(text: "Foo");
235 cursor.insertBlock();
236 cursor.insertBlock();
237 hl->highlighted = false;
238 cursor.insertBlock();
239 QVERIFY(hl->highlighted);
240}
241
242void tst_QSyntaxHighlighter::setCharFormat()
243{
244 TestHighlighter *hl = new TestHighlighter(doc);
245
246 cursor.insertText(text: "FooBar");
247 cursor.insertBlock();
248 cursor.insertText(text: "Blah");
249 cursor.movePosition(op: QTextCursor::Start);
250 cursor.movePosition(op: QTextCursor::End, QTextCursor::KeepAnchor);
251 QTextCharFormat fmt;
252 fmt.setFontItalic(true);
253 hl->highlighted = false;
254 hl->callCount = 0;
255 cursor.mergeCharFormat(modifier: fmt);
256 QVERIFY(hl->highlighted);
257 QCOMPARE(hl->callCount, 2);
258}
259
260void tst_QSyntaxHighlighter::highlightOnInit()
261{
262 cursor.insertText(text: "Hello");
263 cursor.insertBlock();
264 cursor.insertText(text: "World");
265
266 TestHighlighter *hl = new TestHighlighter(doc);
267 QTRY_VERIFY(hl->highlighted);
268}
269
270void tst_QSyntaxHighlighter::highlightOnInitAndAppend()
271{
272 cursor.insertText(text: "Hello");
273 cursor.insertBlock();
274 cursor.insertText(text: "World");
275
276 TestHighlighter *hl = new TestHighlighter(doc);
277 cursor.insertBlock();
278 cursor.insertText(text: "More text");
279 QTRY_VERIFY(hl->highlighted);
280 QVERIFY(hl->highlightedText.endsWith(doc->toPlainText().remove(QLatin1Char('\n'))));
281}
282
283class StateTestHighlighter : public QSyntaxHighlighter
284{
285public:
286 inline StateTestHighlighter(QTextDocument *parent)
287 : QSyntaxHighlighter(parent), state(0), highlighted(false) {}
288
289 inline void reset()
290 {
291 highlighted = false;
292 state = 0;
293 }
294
295 virtual void highlightBlock(const QString &text)
296 {
297 highlighted = true;
298 if (text == QLatin1String("changestate"))
299 setCurrentBlockState(state++);
300 }
301
302 int state;
303 bool highlighted;
304};
305
306void tst_QSyntaxHighlighter::stopHighlightingWhenStateDoesNotChange()
307{
308 cursor.insertText(text: "state");
309 cursor.insertBlock();
310 cursor.insertText(text: "changestate");
311 cursor.insertBlock();
312 cursor.insertText(text: "keepstate");
313 cursor.insertBlock();
314 cursor.insertText(text: "changestate");
315 cursor.insertBlock();
316 cursor.insertText(text: "changestate");
317
318 StateTestHighlighter *hl = new StateTestHighlighter(doc);
319 QTRY_VERIFY(hl->highlighted);
320
321 hl->reset();
322
323 // turn the text of the first block into 'changestate'
324 cursor.movePosition(op: QTextCursor::Start);
325 cursor.insertText(text: "change");
326
327 // verify that we highlighted only to the 'keepstate' block,
328 // not beyond
329 QCOMPARE(hl->state, 2);
330}
331
332void tst_QSyntaxHighlighter::unindent()
333{
334 const QString spaces(" ");
335 const QString text("Foobar");
336 QString plainText;
337 for (int i = 0; i < 5; ++i) {
338 cursor.insertText(text: spaces + text);
339 cursor.insertBlock();
340
341 plainText += spaces;
342 plainText += text;
343 plainText += QLatin1Char('\n');
344 }
345 QCOMPARE(doc->toPlainText(), plainText);
346
347 TestHighlighter *hl = new TestHighlighter(doc);
348 QTRY_VERIFY(hl->highlighted);
349 hl->callCount = 0;
350
351 cursor.movePosition(op: QTextCursor::Start);
352 cursor.beginEditBlock();
353
354 plainText.clear();
355 for (int i = 0; i < 5; ++i) {
356 cursor.movePosition(op: QTextCursor::NextCharacter, QTextCursor::KeepAnchor, n: 4);
357 cursor.removeSelectedText();
358 cursor.movePosition(op: QTextCursor::NextBlock);
359
360 plainText += text;
361 plainText += QLatin1Char('\n');
362 }
363
364 cursor.endEditBlock();
365 QCOMPARE(doc->toPlainText(), plainText);
366 QCOMPARE(hl->callCount, 5);
367}
368
369void tst_QSyntaxHighlighter::highlightToEndOfDocument()
370{
371 TestHighlighter *hl = new TestHighlighter(doc);
372 hl->callCount = 0;
373
374 cursor.movePosition(op: QTextCursor::Start);
375 cursor.beginEditBlock();
376
377 cursor.insertText(text: "Hello");
378 cursor.insertBlock();
379 cursor.insertBlock();
380 cursor.insertText(text: "World");
381 cursor.insertBlock();
382
383 cursor.endEditBlock();
384
385 QCOMPARE(hl->callCount, 4);
386}
387
388void tst_QSyntaxHighlighter::highlightToEndOfDocument2()
389{
390 TestHighlighter *hl = new TestHighlighter(doc);
391 hl->callCount = 0;
392
393 cursor.movePosition(op: QTextCursor::End);
394 cursor.beginEditBlock();
395 QTextBlockFormat fmt;
396 fmt.setAlignment(Qt::AlignLeft);
397 cursor.setBlockFormat(fmt);
398 cursor.insertText(text: "Three\nLines\nHere");
399 cursor.endEditBlock();
400
401 QCOMPARE(hl->callCount, 3);
402}
403
404void tst_QSyntaxHighlighter::preservePreeditArea()
405{
406 QVector<QTextLayout::FormatRange> formats;
407 QTextLayout::FormatRange range;
408 range.start = 0;
409 range.length = 8;
410 range.format.setForeground(Qt::blue);
411 formats << range;
412 range.start = 9;
413 range.length = 1;
414 range.format.setForeground(Qt::red);
415 formats << range;
416 TestHighlighter *hl = new TestHighlighter(formats, doc);
417
418 doc->setPlainText("Hello World");
419 cursor.movePosition(op: QTextCursor::Start);
420
421 QTextLayout *layout = cursor.block().layout();
422
423 layout->setPreeditArea(position: 5, text: QString("foo"));
424 range.start = 5;
425 range.length = 3;
426 range.format.setFontUnderline(true);
427 formats.clear();
428 formats << range;
429
430 hl->callCount = 0;
431
432 cursor.beginEditBlock();
433 layout->setFormats(formats);
434 cursor.endEditBlock();
435
436 QCOMPARE(hl->callCount, 1);
437
438 formats = layout->formats();
439 QCOMPARE(formats.count(), 3);
440
441 range = formats.at(i: 0);
442
443 QCOMPARE(range.start, 5);
444 QCOMPARE(range.length, 3);
445 QVERIFY(range.format.fontUnderline());
446
447 range = formats.at(i: 1);
448 QCOMPARE(range.start, 0);
449 QCOMPARE(range.length, 8 + 3);
450
451 range = formats.at(i: 2);
452 QCOMPARE(range.start, 9 + 3);
453 QCOMPARE(range.length, 1);
454}
455
456void tst_QSyntaxHighlighter::task108530()
457{
458 TestHighlighter *hl = new TestHighlighter(doc);
459
460 cursor.insertText(text: "test");
461 hl->callCount = 0;
462 hl->highlightedText.clear();
463 cursor.movePosition(op: QTextCursor::Start);
464 cursor.insertBlock();
465
466 QCOMPARE(hl->highlightedText, QString("test"));
467 QCOMPARE(hl->callCount, 2);
468}
469
470void tst_QSyntaxHighlighter::avoidUnnecessaryRehighlight()
471{
472 TestHighlighter *hl = new TestHighlighter(doc);
473 QVERIFY(!hl->highlighted);
474
475 doc->setPlainText("Hello World");
476 QVERIFY(hl->highlighted);
477
478 hl->highlighted = false;
479 QCoreApplication::processEvents();
480 QVERIFY(!hl->highlighted);
481}
482
483void tst_QSyntaxHighlighter::avoidUnnecessaryDelayedRehighlight()
484{
485 // Having text in the document before creating the highlighter starts the delayed rehighlight
486 cursor.insertText(text: "Hello World");
487
488 TestHighlighter *hl = new TestHighlighter(doc);
489 QVERIFY(!hl->highlighted);
490
491 hl->rehighlight();
492 QVERIFY(hl->highlighted);
493
494 hl->highlighted = false;
495 // Process events, including delayed rehighlight emission
496 QCoreApplication::processEvents();
497 // Should be cancelled and no extra rehighlight should be done
498 QVERIFY(!hl->highlighted);
499}
500
501void tst_QSyntaxHighlighter::noContentsChangedDuringHighlight()
502{
503 QVector<QTextLayout::FormatRange> formats;
504 QTextLayout::FormatRange range;
505 range.start = 0;
506 range.length = 10;
507 range.format.setForeground(Qt::blue);
508 formats.append(t: range);
509
510 TestHighlighter *hl = new TestHighlighter(formats, doc);
511
512 lout->documentChangedCalled = false;
513 QTextCursor cursor(doc);
514
515 QSignalSpy contentsChangedSpy(doc, SIGNAL(contentsChanged()));
516 cursor.insertText(text: "Hello World");
517
518 QCOMPARE(contentsChangedSpy.count(), 1);
519 QVERIFY(hl->highlighted);
520 QVERIFY(lout->documentChangedCalled);
521}
522
523void tst_QSyntaxHighlighter::rehighlight()
524{
525 TestHighlighter *hl = new TestHighlighter(doc);
526 hl->callCount = 0;
527 doc->setPlainText("Hello");
528 hl->callCount = 0;
529 hl->rehighlight();
530 QCOMPARE(hl->callCount, 1);
531}
532
533void tst_QSyntaxHighlighter::rehighlightBlock()
534{
535 TestHighlighter *hl = new TestHighlighter(doc);
536
537 cursor.movePosition(op: QTextCursor::Start);
538 cursor.beginEditBlock();
539 cursor.insertText(text: "Hello");
540 cursor.insertBlock();
541 cursor.insertText(text: "World");
542 cursor.endEditBlock();
543
544 hl->callCount = 0;
545 hl->highlightedText.clear();
546 QTextBlock block = doc->begin();
547 hl->rehighlightBlock(block);
548
549 QCOMPARE(hl->highlightedText, QString("Hello"));
550 QCOMPARE(hl->callCount, 1);
551
552 hl->callCount = 0;
553 hl->highlightedText.clear();
554 hl->rehighlightBlock(block: block.next());
555
556 QCOMPARE(hl->highlightedText, QString("World"));
557 QCOMPARE(hl->callCount, 1);
558}
559
560#ifndef QT_NO_WIDGETS
561void tst_QSyntaxHighlighter::textEditParent()
562{
563 QTextEdit textEdit;
564 TestHighlighter *hl = new TestHighlighter(&textEdit);
565 QCOMPARE(hl->document(), textEdit.document());
566}
567#endif
568
569QTEST_MAIN(tst_QSyntaxHighlighter)
570#include "tst_qsyntaxhighlighter.moc"
571

source code of qtbase/tests/auto/gui/text/qsyntaxhighlighter/tst_qsyntaxhighlighter.cpp