// Copyright (C) 2016 The Qt Company Ltd. // SPDX-License-Identifier: LicenseRef-Qt-Commercial OR GPL-3.0-only #include #include #include #include #include #include #include #ifndef QT_NO_WIDGETS #include #endif class QTestDocumentLayout : public QAbstractTextDocumentLayout { Q_OBJECT public: inline QTestDocumentLayout(QTextDocument *doc) : QAbstractTextDocumentLayout(doc), documentChangedCalled(false) {} virtual void draw(QPainter *, const QAbstractTextDocumentLayout::PaintContext &) override {} virtual int hitTest(const QPointF &, Qt::HitTestAccuracy ) const override { return 0; } virtual void documentChanged(int, int, int) override { documentChangedCalled = true; } virtual int pageCount() const override { return 1; } virtual QSizeF documentSize() const override { return QSize(); } virtual QRectF frameBoundingRect(QTextFrame *) const override { return QRectF(); } virtual QRectF blockBoundingRect(const QTextBlock &) const override { return QRectF(); } bool documentChangedCalled; }; class tst_QSyntaxHighlighter : public QObject { Q_OBJECT private slots: void init(); void cleanup(); void basic(); void basicTwo(); void removeFormatsOnDelete(); void emptyBlocks(); void setCharFormat(); void highlightOnInit(); void highlightOnInitAndAppend(); void stopHighlightingWhenStateDoesNotChange(); void unindent(); void highlightToEndOfDocument(); void highlightToEndOfDocument2(); void preservePreeditArea(); void task108530(); void avoidUnnecessaryRehighlight(); void avoidUnnecessaryDelayedRehighlight(); void noContentsChangedDuringHighlight(); void rehighlight(); void rehighlightBlock(); #ifndef QT_NO_WIDGETS void textEditParent(); #endif private: QTextDocument *doc; QTestDocumentLayout *lout; QTextCursor cursor; }; void tst_QSyntaxHighlighter::init() { doc = new QTextDocument; lout = new QTestDocumentLayout(doc); doc->setDocumentLayout(lout); cursor = QTextCursor(doc); } void tst_QSyntaxHighlighter::cleanup() { delete doc; doc = 0; } class TestHighlighter : public QSyntaxHighlighter { public: inline TestHighlighter(const QList &fmts, QTextDocument *parent) : QSyntaxHighlighter(parent), formats(fmts), highlighted(false), callCount(0) { } inline TestHighlighter(QObject *parent) : QSyntaxHighlighter(parent) {} inline TestHighlighter(QTextDocument *parent) : QSyntaxHighlighter(parent), highlighted(false), callCount(0) {} virtual void highlightBlock(const QString &text) override { for (int i = 0; i < formats.size(); ++i) { const QTextLayout::FormatRange &range = formats.at(i); setFormat(range.start, range.length, range.format); } highlighted = true; highlightedText += text; ++callCount; } QList formats; bool highlighted; int callCount; QString highlightedText; }; void tst_QSyntaxHighlighter::basic() { QList formats; QTextLayout::FormatRange range; range.start = 0; range.length = 2; range.format.setForeground(Qt::blue); formats.append(range); range.start = 4; range.length = 2; range.format.setFontItalic(true); formats.append(range); range.start = 9; range.length = 2; range.format.setFontUnderline(true); formats.append(range); TestHighlighter *hl = new TestHighlighter(formats, doc); lout->documentChangedCalled = false; doc->setPlainText("Hello World"); QVERIFY(hl->highlighted); QVERIFY(lout->documentChangedCalled); QCOMPARE(doc->begin().layout()->formats(), formats); } class CommentTestHighlighter : public QSyntaxHighlighter { public: inline CommentTestHighlighter(QTextDocument *parent) : QSyntaxHighlighter(parent), highlighted(false) {} inline void reset() { highlighted = false; } virtual void highlightBlock(const QString &text) override { QTextCharFormat commentFormat; commentFormat.setForeground(Qt::darkGreen); commentFormat.setFontWeight(QFont::StyleItalic); commentFormat.setFontFixedPitch(true); int textLength = text.size(); if (text.startsWith(QLatin1Char(';'))){ // The entire line is a comment setFormat(0, textLength, commentFormat); highlighted = true; } } bool highlighted; }; void tst_QSyntaxHighlighter::basicTwo() { // Done for task 104409 CommentTestHighlighter *hl = new CommentTestHighlighter(doc); doc->setPlainText("; a test"); QVERIFY(hl->highlighted); QVERIFY(lout->documentChangedCalled); } void tst_QSyntaxHighlighter::removeFormatsOnDelete() { QList formats; QTextLayout::FormatRange range; range.start = 0; range.length = 9; range.format.setForeground(Qt::blue); formats.append(range); TestHighlighter *hl = new TestHighlighter(formats, doc); lout->documentChangedCalled = false; doc->setPlainText("Hello World"); QVERIFY(hl->highlighted); QVERIFY(lout->documentChangedCalled); lout->documentChangedCalled = false; QVERIFY(!doc->begin().layout()->formats().isEmpty()); delete hl; QVERIFY(doc->begin().layout()->formats().isEmpty()); QVERIFY(lout->documentChangedCalled); } void tst_QSyntaxHighlighter::emptyBlocks() { TestHighlighter *hl = new TestHighlighter(doc); cursor.insertText("Foo"); cursor.insertBlock(); cursor.insertBlock(); hl->highlighted = false; cursor.insertBlock(); QVERIFY(hl->highlighted); } void tst_QSyntaxHighlighter::setCharFormat() { TestHighlighter *hl = new TestHighlighter(doc); cursor.insertText("FooBar"); cursor.insertBlock(); cursor.insertText("Blah"); cursor.movePosition(QTextCursor::Start); cursor.movePosition(QTextCursor::End, QTextCursor::KeepAnchor); QTextCharFormat fmt; fmt.setFontItalic(true); hl->highlighted = false; hl->callCount = 0; cursor.mergeCharFormat(fmt); QVERIFY(hl->highlighted); QCOMPARE(hl->callCount, 2); } void tst_QSyntaxHighlighter::highlightOnInit() { cursor.insertText("Hello"); cursor.insertBlock(); cursor.insertText("World"); TestHighlighter *hl = new TestHighlighter(doc); QTRY_VERIFY(hl->highlighted); } void tst_QSyntaxHighlighter::highlightOnInitAndAppend() { cursor.insertText("Hello"); cursor.insertBlock(); cursor.insertText("World"); TestHighlighter *hl = new TestHighlighter(doc); cursor.insertBlock(); cursor.insertText("More text"); QTRY_VERIFY(hl->highlighted); QVERIFY(hl->highlightedText.endsWith(doc->toPlainText().remove(QLatin1Char('\n')))); } class StateTestHighlighter : public QSyntaxHighlighter { public: inline StateTestHighlighter(QTextDocument *parent) : QSyntaxHighlighter(parent), state(0), highlighted(false) {} inline void reset() { highlighted = false; state = 0; } virtual void highlightBlock(const QString &text) override { highlighted = true; if (text == QLatin1String("changestate")) setCurrentBlockState(state++); } int state; bool highlighted; }; void tst_QSyntaxHighlighter::stopHighlightingWhenStateDoesNotChange() { cursor.insertText("state"); cursor.insertBlock(); cursor.insertText("changestate"); cursor.insertBlock(); cursor.insertText("keepstate"); cursor.insertBlock(); cursor.insertText("changestate"); cursor.insertBlock(); cursor.insertText("changestate"); StateTestHighlighter *hl = new StateTestHighlighter(doc); QTRY_VERIFY(hl->highlighted); hl->reset(); // turn the text of the first block into 'changestate' cursor.movePosition(QTextCursor::Start); cursor.insertText("change"); // verify that we highlighted only to the 'keepstate' block, // not beyond QCOMPARE(hl->state, 2); } void tst_QSyntaxHighlighter::unindent() { const QString spaces(" "); const QString text("Foobar"); QString plainText; for (int i = 0; i < 5; ++i) { cursor.insertText(spaces + text); cursor.insertBlock(); plainText += spaces; plainText += text; plainText += QLatin1Char('\n'); } QCOMPARE(doc->toPlainText(), plainText); TestHighlighter *hl = new TestHighlighter(doc); QTRY_VERIFY(hl->highlighted); hl->callCount = 0; cursor.movePosition(QTextCursor::Start); cursor.beginEditBlock(); plainText.clear(); for (int i = 0; i < 5; ++i) { cursor.movePosition(QTextCursor::NextCharacter, QTextCursor::KeepAnchor, 4); cursor.removeSelectedText(); cursor.movePosition(QTextCursor::NextBlock); plainText += text; plainText += QLatin1Char('\n'); } cursor.endEditBlock(); QCOMPARE(doc->toPlainText(), plainText); QCOMPARE(hl->callCount, 5); } void tst_QSyntaxHighlighter::highlightToEndOfDocument() { TestHighlighter *hl = new TestHighlighter(doc); hl->callCount = 0; cursor.movePosition(QTextCursor::Start); cursor.beginEditBlock(); cursor.insertText("Hello"); cursor.insertBlock(); cursor.insertBlock(); cursor.insertText("World"); cursor.insertBlock(); cursor.endEditBlock(); QCOMPARE(hl->callCount, 4); } void tst_QSyntaxHighlighter::highlightToEndOfDocument2() { TestHighlighter *hl = new TestHighlighter(doc); hl->callCount = 0; cursor.movePosition(QTextCursor::End); cursor.beginEditBlock(); QTextBlockFormat fmt; fmt.setAlignment(Qt::AlignLeft); cursor.setBlockFormat(fmt); cursor.insertText("Three\nLines\nHere"); cursor.endEditBlock(); QCOMPARE(hl->callCount, 3); } void tst_QSyntaxHighlighter::preservePreeditArea() { QList formats; QTextLayout::FormatRange range; range.start = 0; range.length = 8; range.format.setForeground(Qt::blue); formats << range; range.start = 9; range.length = 1; range.format.setForeground(Qt::red); formats << range; TestHighlighter *hl = new TestHighlighter(formats, doc); doc->setPlainText("Hello World"); cursor.movePosition(QTextCursor::Start); QTextLayout *layout = cursor.block().layout(); layout->setPreeditArea(5, QString("foo")); range.start = 5; range.length = 3; range.format.setFontUnderline(true); formats.clear(); formats << range; hl->callCount = 0; cursor.beginEditBlock(); layout->setFormats(formats); cursor.endEditBlock(); QCOMPARE(hl->callCount, 1); formats = layout->formats(); QCOMPARE(formats.size(), 3); range = formats.at(0); QCOMPARE(range.start, 5); QCOMPARE(range.length, 3); QVERIFY(range.format.fontUnderline()); range = formats.at(1); QCOMPARE(range.start, 0); QCOMPARE(range.length, 8 + 3); range = formats.at(2); QCOMPARE(range.start, 9 + 3); QCOMPARE(range.length, 1); } void tst_QSyntaxHighlighter::task108530() { TestHighlighter *hl = new TestHighlighter(doc); cursor.insertText("test"); hl->callCount = 0; hl->highlightedText.clear(); cursor.movePosition(QTextCursor::Start); cursor.insertBlock(); QCOMPARE(hl->highlightedText, QString("test")); QCOMPARE(hl->callCount, 2); } void tst_QSyntaxHighlighter::avoidUnnecessaryRehighlight() { TestHighlighter *hl = new TestHighlighter(doc); QVERIFY(!hl->highlighted); doc->setPlainText("Hello World"); QVERIFY(hl->highlighted); hl->highlighted = false; QCoreApplication::processEvents(); QVERIFY(!hl->highlighted); } void tst_QSyntaxHighlighter::avoidUnnecessaryDelayedRehighlight() { // Having text in the document before creating the highlighter starts the delayed rehighlight cursor.insertText("Hello World"); TestHighlighter *hl = new TestHighlighter(doc); QVERIFY(!hl->highlighted); hl->rehighlight(); QVERIFY(hl->highlighted); hl->highlighted = false; // Process events, including delayed rehighlight emission QCoreApplication::processEvents(); // Should be cancelled and no extra rehighlight should be done QVERIFY(!hl->highlighted); } void tst_QSyntaxHighlighter::noContentsChangedDuringHighlight() { QList formats; QTextLayout::FormatRange range; range.start = 0; range.length = 10; range.format.setForeground(Qt::blue); formats.append(range); TestHighlighter *hl = new TestHighlighter(formats, doc); lout->documentChangedCalled = false; QTextCursor cursor(doc); QSignalSpy contentsChangedSpy(doc, SIGNAL(contentsChanged())); cursor.insertText("Hello World"); QCOMPARE(contentsChangedSpy.size(), 1); QVERIFY(hl->highlighted); QVERIFY(lout->documentChangedCalled); } void tst_QSyntaxHighlighter::rehighlight() { TestHighlighter *hl = new TestHighlighter(doc); hl->callCount = 0; doc->setPlainText("Hello"); hl->callCount = 0; hl->rehighlight(); QCOMPARE(hl->callCount, 1); } void tst_QSyntaxHighlighter::rehighlightBlock() { TestHighlighter *hl = new TestHighlighter(doc); cursor.movePosition(QTextCursor::Start); cursor.beginEditBlock(); cursor.insertText("Hello"); cursor.insertBlock(); cursor.insertText("World"); cursor.endEditBlock(); hl->callCount = 0; hl->highlightedText.clear(); QTextBlock block = doc->begin(); hl->rehighlightBlock(block); QCOMPARE(hl->highlightedText, QString("Hello")); QCOMPARE(hl->callCount, 1); hl->callCount = 0; hl->highlightedText.clear(); hl->rehighlightBlock(block.next()); QCOMPARE(hl->highlightedText, QString("World")); QCOMPARE(hl->callCount, 1); } #ifndef QT_NO_WIDGETS void tst_QSyntaxHighlighter::textEditParent() { QTextEdit textEdit; TestHighlighter *hl = new TestHighlighter(&textEdit); QCOMPARE(hl->document(), textEdit.document()); } #endif QTEST_MAIN(tst_QSyntaxHighlighter) #include "tst_qsyntaxhighlighter.moc"