File indexing completed on 2024-04-28 15:30:09

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2010-2018 Dominik Haumann <dhaumann@kde.org>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "katedocument_test.h"
0009 #include "moc_katedocument_test.cpp"
0010 
0011 #include <kateconfig.h>
0012 #include <katedocument.h>
0013 #include <kateglobal.h>
0014 #include <kateview.h>
0015 #include <ktexteditor/movingcursor.h>
0016 
0017 #include <QRegularExpression>
0018 #include <QSignalSpy>
0019 #include <QTemporaryFile>
0020 #include <QtTestWidgets>
0021 
0022 #include <stdio.h>
0023 
0024 /// TODO: is there a FindValgrind cmake command we could use to
0025 ///      define this automatically?
0026 // comment this out and run the test case with:
0027 //   valgrind --tool=callgrind --instr-atstart=no ./katedocument_test testSetTextPerformance
0028 // or similar
0029 //
0030 // #define USE_VALGRIND
0031 
0032 #ifdef USE_VALGRIND
0033 #include <valgrind/callgrind.h>
0034 #endif
0035 
0036 using namespace KTextEditor;
0037 
0038 QTEST_MAIN(KateDocumentTest)
0039 
0040 class MovingRangeInvalidator : public QObject
0041 {
0042     Q_OBJECT
0043 public:
0044     explicit MovingRangeInvalidator(QObject *parent = nullptr)
0045         : QObject(parent)
0046     {
0047     }
0048 
0049     void addRange(MovingRange *range)
0050     {
0051         m_ranges << range;
0052     }
0053     QList<MovingRange *> ranges() const
0054     {
0055         return m_ranges;
0056     }
0057 
0058 public Q_SLOTS:
0059     void aboutToInvalidateMovingInterfaceContent()
0060     {
0061         qDeleteAll(m_ranges);
0062         m_ranges.clear();
0063     }
0064 
0065 private:
0066     QList<MovingRange *> m_ranges;
0067 };
0068 
0069 KateDocumentTest::KateDocumentTest()
0070     : QObject()
0071 {
0072 }
0073 
0074 KateDocumentTest::~KateDocumentTest()
0075 {
0076 }
0077 
0078 void KateDocumentTest::initTestCase()
0079 {
0080     KTextEditor::EditorPrivate::enableUnitTestMode();
0081 }
0082 
0083 // tests:
0084 // KTextEditor::DocumentPrivate::insertText with word wrap enabled. It is checked whether the
0085 // text is correctly wrapped and whether the moving cursors maintain the correct
0086 // position.
0087 // see also: https://bugs.kde.org/show_bug.cgi?id=168534
0088 void KateDocumentTest::testWordWrap()
0089 {
0090     KTextEditor::DocumentPrivate doc(false, false);
0091     doc.setWordWrap(true);
0092     doc.setWordWrapAt(80);
0093 
0094     const QString content = QLatin1String(".........1.........2.........3.........4.........5.........6 ........7 ........8");
0095 
0096     // space after 7 is now kept
0097     // else we kill indentation...
0098     const QString firstWrap = QLatin1String(".........1.........2.........3.........4.........5.........6 ........7 \n....x....8");
0099 
0100     // space after 6 is now kept
0101     // else we kill indentation...
0102     const QString secondWrap = QLatin1String(".........1.........2.........3.........4.........5.........6 \n....ooooooooooo....7 ....x....8");
0103 
0104     doc.setText(content);
0105     MovingCursor *c = doc.newMovingCursor(Cursor(0, 75), MovingCursor::MoveOnInsert);
0106 
0107     QCOMPARE(doc.text(), content);
0108     QCOMPARE(c->toCursor(), Cursor(0, 75));
0109 
0110     // type a character at (0, 75)
0111     doc.insertText(c->toCursor(), QLatin1String("x"));
0112     QCOMPARE(doc.text(), firstWrap);
0113     QCOMPARE(c->toCursor(), Cursor(1, 5));
0114 
0115     // set cursor to (0, 65) and type "ooooooooooo"
0116     c->setPosition(Cursor(0, 65));
0117     doc.insertText(c->toCursor(), QLatin1String("ooooooooooo"));
0118     QCOMPARE(doc.text(), secondWrap);
0119     QCOMPARE(c->toCursor(), Cursor(1, 15));
0120 }
0121 
0122 void KateDocumentTest::testWrapParagraph()
0123 {
0124     // Each paragraph must be kept as an own but re-wrapped nicely
0125     KTextEditor::DocumentPrivate doc(false, false);
0126     doc.setWordWrapAt(30); // Keep needed test data small
0127 
0128     const QString before =
0129         QLatin1String("aaaaa a aaaa\naaaaa aaa aa aaaa aaaa \naaaa a aaa aaaaaaa a aaaa\n\nxxxxx x\nxxxx xxxxx\nxxx xx xxxx \nxxxx xxxx x xxx xxxxxxx x xxxx");
0130     const QString after =
0131         QLatin1String("aaaaa a aaaa aaaaa aaa aa aaaa \naaaa aaaa a aaa aaaaaaa a aaaa\n\nxxxxx x xxxx xxxxx xxx xx xxxx \nxxxx xxxx x xxx xxxxxxx x xxxx");
0132 
0133     doc.setWordWrap(false); // First we try with disabled hard wrap
0134     doc.setText(before);
0135     doc.wrapParagraph(0, doc.lines() - 1);
0136     QCOMPARE(doc.text(), after);
0137 
0138     doc.setWordWrap(true); // Test again with enabled hard wrap, that had cause trouble due to twice wrapping
0139     doc.setText(before);
0140     doc.wrapParagraph(0, doc.lines() - 1);
0141     QCOMPARE(doc.text(), after);
0142 }
0143 
0144 void KateDocumentTest::testReplaceQStringList()
0145 {
0146     KTextEditor::DocumentPrivate doc(false, false);
0147     doc.setWordWrap(false);
0148     doc.setText(
0149         QLatin1String("asdf\n"
0150                       "foo\n"
0151                       "foo\n"
0152                       "bar\n"));
0153     doc.replaceText(Range(1, 0, 3, 0), {"new", "text", ""}, false);
0154     QCOMPARE(doc.text(),
0155              QLatin1String("asdf\n"
0156                            "new\n"
0157                            "text\n"
0158                            "bar\n"));
0159 }
0160 
0161 void KateDocumentTest::testMovingInterfaceSignals()
0162 {
0163     KTextEditor::DocumentPrivate *doc = new KTextEditor::DocumentPrivate;
0164     QSignalSpy aboutToDeleteSpy(doc, &KTextEditor::DocumentPrivate::aboutToDeleteMovingInterfaceContent);
0165     QSignalSpy aboutToInvalidateSpy(doc, &KTextEditor::DocumentPrivate::aboutToInvalidateMovingInterfaceContent);
0166 
0167     QCOMPARE(doc->revision(), qint64(0));
0168 
0169     QCOMPARE(aboutToInvalidateSpy.count(), 0);
0170     QCOMPARE(aboutToDeleteSpy.count(), 0);
0171 
0172     QTemporaryFile f;
0173     f.open();
0174     doc->openUrl(QUrl::fromLocalFile(f.fileName()));
0175     QCOMPARE(doc->revision(), qint64(0));
0176     // TODO: gets emitted once in closeFile and once in openFile - is that OK?
0177     QCOMPARE(aboutToInvalidateSpy.count(), 2);
0178     QCOMPARE(aboutToDeleteSpy.count(), 0);
0179 
0180     doc->documentReload();
0181     QCOMPARE(doc->revision(), qint64(0));
0182     QCOMPARE(aboutToInvalidateSpy.count(), 4);
0183     // TODO: gets emitted once in closeFile and once in openFile - is that OK?
0184     QCOMPARE(aboutToDeleteSpy.count(), 0);
0185 
0186     delete doc;
0187     QCOMPARE(aboutToInvalidateSpy.count(), 4);
0188     QCOMPARE(aboutToDeleteSpy.count(), 1);
0189 }
0190 
0191 void KateDocumentTest::testSetTextPerformance()
0192 {
0193     const int lines = 150;
0194     const int columns = 80;
0195     const int rangeLength = 4;
0196     const int rangeGap = 1;
0197 
0198     Q_ASSERT(columns % (rangeLength + rangeGap) == 0);
0199 
0200     KTextEditor::DocumentPrivate doc;
0201     MovingRangeInvalidator invalidator;
0202     connect(&doc,
0203             &KTextEditor::DocumentPrivate::aboutToInvalidateMovingInterfaceContent,
0204             &invalidator,
0205             &MovingRangeInvalidator::aboutToInvalidateMovingInterfaceContent);
0206 
0207     QString text;
0208     QVector<Range> ranges;
0209     ranges.reserve(lines * columns / (rangeLength + rangeGap));
0210     const QString line = QString().fill('a', columns);
0211     for (int l = 0; l < lines; ++l) {
0212         text.append(line);
0213         text.append('\n');
0214         for (int c = 0; c < columns; c += rangeLength + rangeGap) {
0215             ranges << Range(l, c, l, c + rangeLength);
0216         }
0217     }
0218 
0219     // replace
0220     QBENCHMARK {
0221         // init
0222         doc.setText(text);
0223         for (const Range &range : std::as_const(ranges)) {
0224             invalidator.addRange(doc.newMovingRange(range));
0225         }
0226         QCOMPARE(invalidator.ranges().size(), ranges.size());
0227 
0228 #ifdef USE_VALGRIND
0229         CALLGRIND_START_INSTRUMENTATION
0230 #endif
0231 
0232         doc.setText(text);
0233 
0234 #ifdef USE_VALGRIND
0235         CALLGRIND_STOP_INSTRUMENTATION
0236 #endif
0237 
0238         QCOMPARE(doc.text(), text);
0239         QVERIFY(invalidator.ranges().isEmpty());
0240     }
0241 }
0242 
0243 void KateDocumentTest::testRemoveTextPerformance()
0244 {
0245     const int lines = 5000;
0246     const int columns = 80;
0247 
0248     KTextEditor::DocumentPrivate doc;
0249 
0250     QString text;
0251     const QString line = QString().fill('a', columns);
0252     for (int l = 0; l < lines; ++l) {
0253         text.append(line);
0254         text.append('\n');
0255     }
0256 
0257     doc.setText(text);
0258 
0259     // replace
0260     QBENCHMARK_ONCE {
0261 #ifdef USE_VALGRIND
0262         CALLGRIND_START_INSTRUMENTATION
0263 #endif
0264 
0265         doc.editStart();
0266 
0267         doc.removeText(doc.documentRange());
0268 
0269         doc.editEnd();
0270 
0271 #ifdef USE_VALGRIND
0272         CALLGRIND_STOP_INSTRUMENTATION
0273 #endif
0274     }
0275 }
0276 
0277 void KateDocumentTest::testForgivingApiUsage()
0278 {
0279     KTextEditor::DocumentPrivate doc;
0280 
0281     QVERIFY(doc.isEmpty());
0282     QVERIFY(doc.replaceText(Range(0, 0, 100, 100), "asdf"));
0283     QCOMPARE(doc.text(), QString("asdf"));
0284     QCOMPARE(doc.lines(), 1);
0285     QVERIFY(doc.replaceText(Range(2, 5, 2, 100), "asdf"));
0286     QCOMPARE(doc.lines(), 3);
0287     QCOMPARE(doc.text(), QLatin1String("asdf\n\n     asdf"));
0288 
0289     QVERIFY(doc.removeText(Range(0, 0, 1000, 1000)));
0290     QVERIFY(doc.removeText(Range(0, 0, 0, 100)));
0291     QVERIFY(doc.isEmpty());
0292     doc.insertText(Cursor(100, 0), "foobar");
0293     QCOMPARE(doc.line(100), QString("foobar"));
0294 
0295     doc.setText("nY\nnYY\n");
0296     QVERIFY(doc.removeText(Range(0, 0, 0, 1000)));
0297 }
0298 
0299 void KateDocumentTest::testAutoBrackets()
0300 {
0301     KTextEditor::DocumentPrivate doc;
0302     auto view = static_cast<KTextEditor::ViewPrivate *>(doc.createView(nullptr));
0303 
0304     auto reset = [&]() {
0305         doc.setText("");
0306         view->setCursorPosition(Cursor(0, 0));
0307     };
0308 
0309     auto typeText = [&](const QString &text) {
0310         for (int i = 0; i < text.size(); ++i) {
0311             doc.typeChars(view, text.at(i));
0312         }
0313     };
0314 
0315     doc.setHighlightingMode("Normal"); // Just to be sure
0316     view->config()->setValue(KateViewConfig::AutoBrackets, true);
0317 
0318     QString testInput;
0319 
0320     testInput = ("(");
0321     typeText(testInput);
0322     QCOMPARE(doc.text(), "()");
0323 
0324     reset();
0325     testInput = ("\"");
0326     typeText(testInput);
0327     QCOMPARE(doc.text(), "\"\"");
0328 
0329     reset();
0330     testInput = ("'");
0331     typeText(testInput);
0332     QCOMPARE(doc.text(), "'"); // In Normal mode there is only one quote to expect
0333 
0334     //
0335     // Switch over to some other mode
0336     //
0337     doc.setHighlightingMode("C++");
0338 
0339     reset();
0340     typeText(testInput);
0341     QCOMPARE(doc.text(), "''"); // Now it must be two
0342 
0343     reset();
0344     testInput = "('')";
0345     typeText(testInput);
0346     // Known bad behaviour in case of nested brackets
0347     QCOMPARE(doc.text(), testInput);
0348 
0349     reset();
0350     testInput = ("foo \"bar\" haz");
0351     typeText(testInput);
0352     QCOMPARE(doc.text(), testInput);
0353     // Simulate afterwards to add quotes, bug 405089
0354     doc.setText("foo \"bar");
0355     typeText("\" haz");
0356     QCOMPARE(doc.text(), testInput);
0357 
0358     // Let's check to add brackets to a selection...
0359     view->setBlockSelection(false);
0360     doc.setText("012xxx678");
0361     view->setSelection(Range(0, 3, 0, 6));
0362     typeText("[");
0363     QCOMPARE(doc.text(), "012[xxx]678");
0364     QCOMPARE(view->selectionRange(), Range(0, 4, 0, 7));
0365 
0366     // ...over multiple lines..
0367     doc.setText("012xxx678\n012xxx678");
0368     view->setSelection(Range(0, 3, 1, 6));
0369     typeText("[");
0370     QCOMPARE(doc.text(), "012[xxx678\n012xxx]678");
0371     QCOMPARE(view->selectionRange(), Range(0, 4, 1, 6));
0372 
0373     // ..once again in in block mode with increased complexity..
0374     view->setBlockSelection(true);
0375     doc.setText("012xxx678\n012xxx678");
0376     view->setSelection(Range(0, 3, 1, 6));
0377     typeText("[(");
0378     QCOMPARE(doc.text(), "012[(xxx)]678\n012[(xxx)]678");
0379     QCOMPARE(view->selectionRange(), Range(0, 5, 1, 8));
0380 
0381     // ..and the same with right->left selection
0382     view->setBlockSelection(true);
0383     doc.setText("012xxx678\n012xxx678");
0384     view->setSelection(Range(0, 6, 1, 3));
0385     typeText("[(");
0386     QCOMPARE(doc.text(), "012[(xxx)]678\n012[(xxx)]678");
0387     QCOMPARE(view->selectionRange(), Range(0, 8, 1, 5));
0388 }
0389 
0390 void KateDocumentTest::testReplaceTabs()
0391 {
0392     KTextEditor::DocumentPrivate doc;
0393     auto view = static_cast<KTextEditor::ViewPrivate *>(doc.createView(nullptr));
0394 
0395     auto reset = [&]() {
0396         doc.setText("  Hi!");
0397         view->setCursorPosition(Cursor(0, 0));
0398     };
0399 
0400     doc.setHighlightingMode("C++");
0401     doc.config()->setTabWidth(4);
0402     doc.config()->setIndentationMode("cppstyle");
0403 
0404     // no replace tabs, no indent pasted text
0405     doc.setConfigValue("replace-tabs", false);
0406     doc.setConfigValue("indent-pasted-text", false);
0407 
0408     reset();
0409     doc.insertText(Cursor(0, 0), "\t");
0410     QCOMPARE(doc.text(), QStringLiteral("\t  Hi!"));
0411 
0412     reset();
0413     doc.typeChars(view, "\t");
0414     QCOMPARE(doc.text(), QStringLiteral("\t  Hi!"));
0415 
0416     reset();
0417     doc.paste(view, "some\ncode\n  3\nhi\n");
0418     QCOMPARE(doc.text(), QStringLiteral("some\ncode\n  3\nhi\n  Hi!"));
0419 
0420     // now, enable replace tabs
0421     doc.setConfigValue("replace-tabs", true);
0422 
0423     reset();
0424     doc.insertText(Cursor(0, 0), "\t");
0425     // calling insertText does not replace tabs
0426     QCOMPARE(doc.text(), QStringLiteral("\t  Hi!"));
0427 
0428     reset();
0429     doc.typeChars(view, "\t");
0430     // typing text replaces tabs
0431     QCOMPARE(doc.text(), QStringLiteral("      Hi!"));
0432 
0433     reset();
0434     doc.paste(view, "\t");
0435     // pasting text does not with indent-pasted off
0436     QCOMPARE(doc.text(), QStringLiteral("\t  Hi!"));
0437 
0438     doc.setConfigValue("indent-pasted-text", true);
0439     doc.setText("int main() {\n    \n}");
0440     view->setCursorPosition(Cursor(1, 4));
0441     doc.paste(view, "\tHi");
0442     // ... and it still does not with indent-pasted on.
0443     // This behaviour is up to discussion.
0444     // \t survives as we don't indent in the given case anymore, see 077dfe954699c674d2c34caf380199a4af7d184a
0445     QCOMPARE(doc.text(), QStringLiteral("int main() {\n    \tHi\n}"));
0446 
0447     reset();
0448     doc.paste(view, "some\ncode\n  3\nhi");
0449     QCOMPARE(doc.text(), QStringLiteral("some\ncode\n3\nhi  Hi!"));
0450 }
0451 
0452 /**
0453  * Provides slots to check data sent in specific signals. Slot names are derived from corresponding test names.
0454  */
0455 class SignalHandler : public QObject
0456 {
0457     Q_OBJECT
0458 public Q_SLOTS:
0459     void slotMultipleLinesRemoved(KTextEditor::Document *, const KTextEditor::Range &, const QString &oldText)
0460     {
0461         QCOMPARE(oldText, QString("line2\nline3\n"));
0462     }
0463 
0464     void slotNewlineInserted(KTextEditor::Document *, const KTextEditor::Range &range)
0465     {
0466         QCOMPARE(range, Range(Cursor(1, 4), Cursor(2, 0)));
0467     }
0468 };
0469 
0470 void KateDocumentTest::testRemoveMultipleLines()
0471 {
0472     KTextEditor::DocumentPrivate doc;
0473 
0474     doc.setText(
0475         "line1\n"
0476         "line2\n"
0477         "line3\n"
0478         "line4\n");
0479 
0480     SignalHandler handler;
0481     connect(&doc, &KTextEditor::DocumentPrivate::textRemoved, &handler, &SignalHandler::slotMultipleLinesRemoved);
0482     doc.removeText(Range(1, 0, 3, 0));
0483 }
0484 
0485 void KateDocumentTest::testInsertNewline()
0486 {
0487     KTextEditor::DocumentPrivate doc;
0488 
0489     doc.setText(
0490         "this is line\n"
0491         "this is line2\n");
0492 
0493     SignalHandler handler;
0494     connect(&doc, &KTextEditor::DocumentPrivate::textInsertedRange, &handler, &SignalHandler::slotNewlineInserted);
0495     doc.editWrapLine(1, 4);
0496 }
0497 
0498 void KateDocumentTest::testInsertAfterEOF()
0499 {
0500     KTextEditor::DocumentPrivate doc;
0501 
0502     doc.setText(
0503         "line0\n"
0504         "line1");
0505 
0506     const QString input = QLatin1String(
0507         "line3\n"
0508         "line4");
0509 
0510     const QString expected = QLatin1String(
0511         "line0\n"
0512         "line1\n"
0513         "\n"
0514         "line3\n"
0515         "line4");
0516 
0517     doc.insertText(KTextEditor::Cursor(3, 0), input);
0518     QCOMPARE(doc.text(), expected);
0519 }
0520 
0521 // we have two different ways of creating the checksum:
0522 // in KateFileLoader and KTextEditor::DocumentPrivate::createDigest. Make
0523 // sure, these two implementations result in the same checksum.
0524 void KateDocumentTest::testDigest()
0525 {
0526     // we will write the test file here to avoid that any line ending conversion for git will break it
0527     const QByteArray fileDigest = "aa22605da164a4e4e55f4c9738cfe1e53d4467f9";
0528     QTemporaryFile file("testDigest");
0529     file.open();
0530     file.write("974d9ab0860c755a4f5686b3b6b429e1efd48a96\ntest\ntest\n\r\n\r\n\r\n");
0531     file.flush();
0532 
0533     // make sure, Kate::TextBuffer and KTextEditor::DocumentPrivate::createDigest() equal
0534     KTextEditor::DocumentPrivate doc;
0535     doc.openUrl(QUrl::fromLocalFile(file.fileName()));
0536     const QByteArray bufferDigest(doc.checksum().toHex());
0537     QVERIFY(doc.createDigest());
0538     const QByteArray docDigest(doc.checksum().toHex());
0539 
0540     QCOMPARE(bufferDigest, fileDigest);
0541     QCOMPARE(docDigest, fileDigest);
0542 }
0543 
0544 void KateDocumentTest::testModelines()
0545 {
0546     // honor document variable indent-width
0547     {
0548         KTextEditor::DocumentPrivate doc;
0549         QCOMPARE(doc.config()->indentationWidth(), 4);
0550         doc.readVariableLine(QStringLiteral("kate: indent-width 3;"));
0551         QCOMPARE(doc.config()->indentationWidth(), 3);
0552     }
0553 
0554     // honor document variable indent-width with * wildcard
0555     {
0556         KTextEditor::DocumentPrivate doc;
0557         QCOMPARE(doc.config()->indentationWidth(), 4);
0558         doc.readVariableLine(QStringLiteral("kate-wildcard(*): indent-width 3;"));
0559         QCOMPARE(doc.config()->indentationWidth(), 3);
0560     }
0561 
0562     // ignore document variable indent-width, since the wildcard does not match
0563     {
0564         KTextEditor::DocumentPrivate doc;
0565         QCOMPARE(doc.config()->indentationWidth(), 4);
0566         doc.readVariableLine(QStringLiteral("kate-wildcard(*.txt): indent-width 3;"));
0567         QCOMPARE(doc.config()->indentationWidth(), 4);
0568     }
0569 
0570     // document variable indent-width, since the wildcard does not match
0571     {
0572         KTextEditor::DocumentPrivate doc;
0573         doc.openUrl(QUrl::fromLocalFile(QLatin1String(TEST_DATA_DIR "modelines.txt")));
0574         QVERIFY(!doc.isEmpty());
0575 
0576         // ignore wrong wildcard
0577         QCOMPARE(doc.config()->indentationWidth(), 4);
0578         doc.readVariableLine(QStringLiteral("kate-wildcard(*.bar): indent-width 3;"));
0579         QCOMPARE(doc.config()->indentationWidth(), 4);
0580 
0581         // read correct wildcard
0582         QCOMPARE(doc.config()->indentationWidth(), 4);
0583         doc.readVariableLine(QStringLiteral("kate-wildcard(*.txt): indent-width 5;"));
0584         QCOMPARE(doc.config()->indentationWidth(), 5);
0585 
0586         // honor correct wildcard
0587         QCOMPARE(doc.config()->indentationWidth(), 5);
0588         doc.readVariableLine(QStringLiteral("kate-wildcard(*.foo;*.txt;*.bar): indent-width 6;"));
0589         QCOMPARE(doc.config()->indentationWidth(), 6);
0590 
0591         // ignore incorrect mimetype
0592         QCOMPARE(doc.config()->indentationWidth(), 6);
0593         doc.readVariableLine(QStringLiteral("kate-mimetype(text/unknown): indent-width 7;"));
0594         QCOMPARE(doc.config()->indentationWidth(), 6);
0595 
0596         // honor correct mimetype
0597         QCOMPARE(doc.config()->indentationWidth(), 6);
0598         doc.readVariableLine(QStringLiteral("kate-mimetype(text/plain): indent-width 8;"));
0599         QCOMPARE(doc.config()->indentationWidth(), 8);
0600 
0601         // honor correct mimetype
0602         QCOMPARE(doc.config()->indentationWidth(), 8);
0603         doc.readVariableLine(QStringLiteral("kate-mimetype(text/foo;text/plain;text/bar): indent-width 9;"));
0604         QCOMPARE(doc.config()->indentationWidth(), 9);
0605     }
0606 }
0607 
0608 void KateDocumentTest::testDefStyleNum()
0609 {
0610     KTextEditor::DocumentPrivate doc;
0611     doc.setText("foo\nbar\nasdf");
0612     QCOMPARE(doc.defStyleNum(0, 0), 0);
0613 }
0614 
0615 void KateDocumentTest::testTypeCharsWithSurrogateAndNewLine()
0616 {
0617     KTextEditor::DocumentPrivate doc;
0618     auto view = static_cast<KTextEditor::ViewPrivate *>(doc.createView(nullptr));
0619     const char32_t surrogateUcs4String[] = {0x1f346, '\n', 0x1f346, 0};
0620     const auto surrogateString = QString::fromUcs4(surrogateUcs4String);
0621     doc.typeChars(view, surrogateString);
0622 
0623     QCOMPARE(doc.text(), surrogateString);
0624 }
0625 
0626 void KateDocumentTest::testRemoveComposedCharacters()
0627 {
0628     KTextEditor::DocumentPrivate doc;
0629     auto view = static_cast<KTextEditor::ViewPrivate *>(doc.createView(nullptr));
0630     view->config()->setValue(KateViewConfig::BackspaceRemoveComposedCharacters, true);
0631     doc.setText(QString::fromUtf8("व्यक्तियों"));
0632     doc.del(view, Cursor(0, 0));
0633 
0634     QCOMPARE(doc.text(), QString::fromUtf8(("क्तियों")));
0635 
0636     view->setCursorPosition({0, 7});
0637     doc.backspace(view);
0638 
0639     QCOMPARE(doc.text(), QString::fromUtf8(("क्ति")));
0640 }
0641 
0642 void KateDocumentTest::testAutoReload()
0643 {
0644     // ATM fails on Windows, mark as such to be able to enforce test success in CI
0645 #ifdef Q_OS_WIN
0646     QSKIP("Fails ATM, please fix");
0647 #endif
0648 
0649     QTemporaryFile file("AutoReloadTestFile");
0650     file.open();
0651     file.write("Hello");
0652     file.flush();
0653 
0654     KTextEditor::DocumentPrivate doc;
0655     auto view = static_cast<KTextEditor::ViewPrivate *>(doc.createView(nullptr));
0656     QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName())));
0657     QCOMPARE(doc.text(), "Hello");
0658     // The cursor should be and stay in the last line...
0659     QCOMPARE(view->cursorPosition().line(), doc.documentEnd().line());
0660 
0661     doc.autoReloadToggled(true);
0662 
0663     // Some magic value. You can wait as long as you like after write to file,
0664     // without to wait before it doesn't work here. It mostly fails with 500,
0665     // it tend to work with 600, but is not guarantied to work even with 800
0666     QTest::qWait(1000);
0667 
0668     file.write("\nTest");
0669     file.flush();
0670 
0671     // Hardcoded delay m_modOnHdTimer in DocumentPrivate
0672     // + min val with which it looks working reliable here
0673     QTest::qWait(1000);
0674     QCOMPARE(doc.text(), "Hello\nTest");
0675     // ...stay in the last line after reload!
0676     QCOMPARE(view->cursorPosition().line(), doc.documentEnd().line());
0677 
0678     // Now we have more then one line, set the cursor to some line != last line...
0679     Cursor c(0, 3);
0680     view->setCursorPosition(c);
0681 
0682     file.write("\nWorld!");
0683     file.flush();
0684 
0685     QTest::qWait(1000);
0686     QCOMPARE(doc.text(), "Hello\nTest\nWorld!");
0687     // ...and ensure we have not move around
0688     QCOMPARE(view->cursorPosition(), c);
0689 }
0690 
0691 void KateDocumentTest::testSearch()
0692 {
0693     /**
0694      * this is the start of some new implementation of searchText that can handle multi-line regex stuff
0695      * just naturally and without always concatenating the full document.
0696      */
0697 
0698     KTextEditor::DocumentPrivate doc;
0699     doc.setText("foo\nbar\nzonk");
0700 
0701     const QRegularExpression pattern(QStringLiteral("ar\nzonk$"), QRegularExpression::MultilineOption | QRegularExpression::UseUnicodePropertiesOption);
0702     int startLine = 0;
0703     int endLine = 2;
0704     QString textToMatch;
0705     int partialMatchLine = -1;
0706     for (int currentLine = startLine; currentLine <= endLine; ++currentLine) {
0707         // if we had a partial match before, we need to keep the old text and append our new line
0708         int matchStartLine = currentLine;
0709         if (partialMatchLine >= 0) {
0710             textToMatch += doc.line(currentLine);
0711             textToMatch += QLatin1Char('\n');
0712             matchStartLine = partialMatchLine;
0713         } else {
0714             textToMatch = doc.line(currentLine);
0715             textToMatch += QLatin1Char('\n');
0716         }
0717 
0718         auto result = pattern.match(textToMatch, 0, QRegularExpression::PartialPreferFirstMatch);
0719         qDebug() << "match" << result.hasMatch() << result.hasPartialMatch() << result.capturedStart() << result.capturedLength();
0720 
0721         if (result.hasMatch()) {
0722             printf("found stuff starting in line %d\n", matchStartLine);
0723             break;
0724         }
0725 
0726         // else: remember if we need to keep text!
0727         if (result.hasPartialMatch()) {
0728             // if we had already a partial match before, keep the line!
0729             if (partialMatchLine >= 0) {
0730             } else {
0731                 partialMatchLine = currentLine;
0732             }
0733         } else {
0734             // we can forget the old text
0735             partialMatchLine = -1;
0736         }
0737     }
0738 }
0739 
0740 void KateDocumentTest::testMatchingBracket_data()
0741 {
0742     QTest::addColumn<QString>("text");
0743     QTest::addColumn<KTextEditor::Cursor>("cursor");
0744     QTest::addColumn<KTextEditor::Range>("match");
0745     QTest::addColumn<int>("maxLines");
0746 
0747     QTest::addRow("invalid") << "asdf\nasdf" << KTextEditor::Cursor(1, 0) << KTextEditor::Range::invalid() << 10;
0748     QTest::addRow("]-before") << "[\n]" << KTextEditor::Cursor(1, 0) << KTextEditor::Range({0, 0}, {1, 0}) << 10;
0749     QTest::addRow("]-after") << "[\n]" << KTextEditor::Cursor(1, 1) << KTextEditor::Range({0, 0}, {1, 0}) << 10;
0750     QTest::addRow("[-before") << "[\n]" << KTextEditor::Cursor(0, 0) << KTextEditor::Range({0, 0}, {1, 0}) << 10;
0751     QTest::addRow("[-after") << "[\n]" << KTextEditor::Cursor(0, 1) << KTextEditor::Range({0, 0}, {1, 0}) << 10;
0752     QTest::addRow("}-before") << "{\n}" << KTextEditor::Cursor(1, 0) << KTextEditor::Range({0, 0}, {1, 0}) << 10;
0753     QTest::addRow("}-after") << "{\n}" << KTextEditor::Cursor(1, 1) << KTextEditor::Range({0, 0}, {1, 0}) << 10;
0754     QTest::addRow("{-before") << "{\n}" << KTextEditor::Cursor(0, 0) << KTextEditor::Range({0, 0}, {1, 0}) << 10;
0755     QTest::addRow("{-after") << "{\n}" << KTextEditor::Cursor(0, 1) << KTextEditor::Range({0, 0}, {1, 0}) << 10;
0756     QTest::addRow(")-before") << "(\n)" << KTextEditor::Cursor(1, 0) << KTextEditor::Range({0, 0}, {1, 0}) << 10;
0757     QTest::addRow(")-after") << "(\n)" << KTextEditor::Cursor(1, 1) << KTextEditor::Range({0, 0}, {1, 0}) << 10;
0758     QTest::addRow("(-before") << "(\n)" << KTextEditor::Cursor(0, 0) << KTextEditor::Range({0, 0}, {1, 0}) << 10;
0759     QTest::addRow("(-after") << "(\n)" << KTextEditor::Cursor(0, 1) << KTextEditor::Range({0, 0}, {1, 0}) << 10;
0760     QTest::addRow("]-maxlines") << "[\n\n]" << KTextEditor::Cursor(1, 0) << KTextEditor::Range::invalid() << 1;
0761 }
0762 
0763 void KateDocumentTest::testMatchingBracket()
0764 {
0765     QFETCH(QString, text);
0766     QFETCH(KTextEditor::Cursor, cursor);
0767     QFETCH(KTextEditor::Range, match);
0768     QFETCH(int, maxLines);
0769 
0770     KTextEditor::DocumentPrivate doc;
0771     doc.setText(text);
0772     QCOMPARE(doc.findMatchingBracket(cursor, maxLines), match);
0773 }
0774 
0775 void KateDocumentTest::testIndentOnPaste()
0776 {
0777     KTextEditor::DocumentPrivate doc;
0778     auto view = static_cast<KTextEditor::ViewPrivate *>(doc.createView(nullptr));
0779 
0780     doc.setHighlightingMode("C++");
0781     doc.config()->setTabWidth(4);
0782     doc.config()->setIndentationMode("cppstyle");
0783 
0784     doc.setConfigValue("indent-pasted-text", true);
0785 
0786     /**
0787      * namespace ns
0788      * {
0789      * class MyClass
0790      */
0791     doc.setText(QStringLiteral("namespace ns\n{\nclass MyClass"));
0792     view->setCursorPosition({2, 5});
0793     doc.paste(view, QStringLiteral(" SOME_MACRO"));
0794     // We have text in the line we are pasting in so the existing indentation shouldn't be disturbed
0795     QCOMPARE(doc.text(), QStringLiteral("namespace ns\n{\nclass SOME_MACRO MyClass"));
0796 
0797     /**
0798      * namespace ns
0799      * {
0800      */
0801     doc.setText(QStringLiteral("namespace ns\n{\n"));
0802     view->setCursorPosition({2, 0});
0803     doc.paste(view, QStringLiteral("class MyClass"));
0804     // We have no text in the line we are pasting in so indentation will be adjusted
0805     QCOMPARE(doc.text(), QStringLiteral("namespace ns\n{\n    class MyClass"));
0806 }
0807 
0808 void KateDocumentTest::testAboutToSave()
0809 {
0810     KTextEditor::DocumentPrivate doc;
0811     const QString thisFile = QString::fromUtf8(__FILE__);
0812     bool opened = doc.openUrl(QUrl::fromLocalFile(thisFile));
0813 
0814     QVERIFY(opened);
0815 
0816     QSignalSpy spy(&doc, &KTextEditor::DocumentPrivate::aboutToSave);
0817     QSignalSpy savedSpy(&doc, &KTextEditor::DocumentPrivate::documentSavedOrUploaded);
0818 
0819     doc.documentSave();
0820 
0821     QVERIFY(spy.count() == 1 || spy.wait());
0822     QVERIFY(savedSpy.count() == 1 || savedSpy.wait());
0823 }
0824 
0825 void KateDocumentTest::testKeepUndoOverReload()
0826 {
0827     // create test document with some simple text
0828     KTextEditor::DocumentPrivate doc;
0829     const QString initialText = QStringLiteral("lala\ntest\nfoobar\n");
0830     doc.setText(initialText);
0831     QCOMPARE(doc.text(), initialText);
0832 
0833     // now: do some editing
0834     const QString insertedText = QStringLiteral("newfirstline\n");
0835     doc.insertText(KTextEditor::Cursor(0, 0), insertedText);
0836     QCOMPARE(doc.text(), insertedText + initialText);
0837 
0838     // test undo/redo
0839     doc.undo();
0840     QCOMPARE(doc.text(), initialText);
0841     doc.redo();
0842     QCOMPARE(doc.text(), insertedText + initialText);
0843 
0844     // save it to some local temporary file, for later reload
0845     QTemporaryFile tmpFile;
0846     tmpFile.open();
0847     QVERIFY(doc.saveAs(QUrl::fromLocalFile(tmpFile.fileName())));
0848 
0849     // first: try if normal reload works
0850     QVERIFY(doc.documentReload());
0851     QCOMPARE(doc.text(), insertedText + initialText);
0852 
0853     // test undo/redo AFTER reload
0854     doc.undo();
0855     QCOMPARE(doc.text(), initialText);
0856     doc.redo();
0857     QCOMPARE(doc.text(), insertedText + initialText);
0858 }
0859 
0860 void KateDocumentTest::testToggleComment()
0861 {
0862     { // BUG: 451471
0863         KTextEditor::DocumentPrivate doc;
0864         QVERIFY(doc.highlightingModes().contains(QStringLiteral("Python")));
0865         doc.setHighlightingMode(QStringLiteral("Python"));
0866         const QString original = QStringLiteral("import hello;\ndef method():");
0867         doc.setText(original);
0868         QVERIFY(doc.lines() == 2);
0869 
0870         doc.commentSelection(doc.documentRange(), {1, 2}, false, DocumentPrivate::ToggleComment);
0871         QCOMPARE(doc.text(), QStringLiteral("# import hello;\n# def method():"));
0872 
0873         doc.commentSelection(doc.documentRange(), {1, 2}, false, DocumentPrivate::ToggleComment);
0874         QCOMPARE(doc.text(), original);
0875     }
0876 
0877     { // Comment C++;
0878         KTextEditor::DocumentPrivate doc;
0879         QVERIFY(doc.highlightingModes().contains(QStringLiteral("C++")));
0880         doc.setHighlightingMode(QStringLiteral("C++"));
0881         QString original = QStringLiteral("#include<iostream>\nint main()\n{\nreturn 0;\n}\n");
0882         doc.setText(original);
0883         QVERIFY(doc.lines() == 6);
0884 
0885         doc.commentSelection(doc.documentRange(), {5, 0}, false, DocumentPrivate::ToggleComment);
0886         QCOMPARE(doc.text(), QStringLiteral("// #include<iostream>\n// int main()\n// {\n// return 0;\n// }\n"));
0887 
0888         doc.commentSelection(doc.documentRange(), {5, 0}, false, DocumentPrivate::ToggleComment);
0889         QCOMPARE(doc.text(), original);
0890 
0891         // Comment just a portion
0892         doc.commentSelection(Range(1, 0, 1, 3), Cursor(1, 3), false, DocumentPrivate::ToggleComment);
0893         QCOMPARE(doc.text(), QStringLiteral("#include<iostream>\n/*int*/ main()\n{\nreturn 0;\n}\n"));
0894         doc.commentSelection(Range(1, 0, 1, 7), Cursor(1, 3), false, DocumentPrivate::ToggleComment);
0895         QCOMPARE(doc.text(), original);
0896 
0897         // mixed, one line commented, one not => both get commented
0898         original = QStringLiteral(" // int main()\n{}");
0899         doc.setText(original);
0900         doc.commentSelection(doc.documentRange(), {1, 2}, false, DocumentPrivate::ToggleComment);
0901         QCOMPARE(doc.text(), QStringLiteral("//  // int main()\n// {}"));
0902         doc.commentSelection(doc.documentRange(), {1, 2}, false, DocumentPrivate::ToggleComment);
0903         // after uncommenting, we get original text back with one line commented
0904         QCOMPARE(doc.text(), original);
0905     }
0906 
0907     { // BUG: 458126
0908         KTextEditor::DocumentPrivate doc;
0909         doc.setText("another:\n\nanother2: hello");
0910         QVERIFY(doc.highlightingModes().contains(QStringLiteral("YAML")));
0911         doc.setHighlightingMode("YAML");
0912         const QString original = doc.text();
0913 
0914         doc.commentSelection(doc.documentRange(), {2, 0}, false, DocumentPrivate::ToggleComment);
0915         QCOMPARE(doc.text(), "# another:\n# \n# another2: hello");
0916 
0917         doc.commentSelection(doc.documentRange(), {2, 0}, false, DocumentPrivate::ToggleComment);
0918         QCOMPARE(doc.text(), original);
0919     }
0920 }
0921 
0922 void KateDocumentTest::testInsertTextTooLargeColumn()
0923 {
0924     KTextEditor::DocumentPrivate doc;
0925     const QString original = QStringLiteral("01234567\n01234567");
0926     doc.setText(original);
0927     QVERIFY(doc.lines() == 2);
0928     QCOMPARE(doc.text(), original);
0929 
0930     // try to insert text with initial \n at wrong column, did trigger invalid call to editWrapLine
0931     doc.insertText(KTextEditor::Cursor(0, 10), QStringLiteral("\nxxxx"));
0932     QCOMPARE(doc.text(), QStringLiteral("01234567  \nxxxx\n01234567"));
0933 }
0934 
0935 void KateDocumentTest::testBug468495()
0936 {
0937     // original
0938     const char o[] = R"(                0123456789abcdefghijkl
0939                 0123456789abcdefghijkl
0940                 012345678901234567890123456789)";
0941     // expected
0942     const char e[] = R"(                0123456789abcdefghijkl
0943 
0944                 0123456789abcdefghijkl
0945                 012345678901234567890123456789)";
0946 
0947     QString original = QString::fromLatin1(o);
0948     KTextEditor::DocumentPrivate doc;
0949     doc.setText(original);
0950     doc.config()->setIndentationMode(QStringLiteral("cstyle"));
0951     KTextEditor::ViewPrivate *v = static_cast<KTextEditor::ViewPrivate *>(doc.createView(nullptr));
0952     v->setCursorPosition({1, 0});
0953     v->keyReturn();
0954 
0955     QString afterIndent = doc.text();
0956     QCOMPARE(QString::fromLatin1(e), afterIndent);
0957 }
0958 
0959 #include "katedocument_test.moc"