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