File indexing completed on 2024-12-01 12:38:41
0001 /* 0002 This file is part of the KDE libraries 0003 SPDX-FileCopyrightText: 2010 Milian Wolff <mail@milianw.de> 0004 0005 SPDX-License-Identifier: LGPL-2.0-or-later 0006 */ 0007 0008 #include "kateview_test.h" 0009 #include "moc_kateview_test.cpp" 0010 0011 #include <katebuffer.h> 0012 #include <kateconfig.h> 0013 #include <katedocument.h> 0014 #include <kateglobal.h> 0015 #include <kateview.h> 0016 #include <kateviewinternal.h> 0017 #include <ktexteditor/message.h> 0018 #include <ktexteditor/movingcursor.h> 0019 0020 #include <QTemporaryFile> 0021 #include <QtTestWidgets> 0022 0023 #define testNewRow() (QTest::newRow(QString("line %1").arg(__LINE__).toLatin1().data())) 0024 0025 using namespace KTextEditor; 0026 0027 QTEST_MAIN(KateViewTest) 0028 0029 KateViewTest::KateViewTest() 0030 : QObject() 0031 { 0032 KTextEditor::EditorPrivate::enableUnitTestMode(); 0033 } 0034 0035 KateViewTest::~KateViewTest() 0036 { 0037 } 0038 0039 void KateViewTest::testCoordinatesToCursor() 0040 { 0041 KTextEditor::DocumentPrivate doc(false, false); 0042 doc.setText("Hi World!\nHi\n"); 0043 0044 KTextEditor::View *view1 = static_cast<KTextEditor::View *>(doc.createView(nullptr)); 0045 view1->resize(400, 300); 0046 view1->show(); 0047 0048 QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(0, 2))), KTextEditor::Cursor(0, 2)); 0049 QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 1))), KTextEditor::Cursor(1, 1)); 0050 // behind end of line should give an invalid cursor 0051 QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 5))), KTextEditor::Cursor::invalid()); 0052 QCOMPARE(view1->cursorToCoordinate(KTextEditor::Cursor(3, 1)), QPoint(-1, -1)); 0053 0054 // check consistency between cursorToCoordinate(view->cursorPosition() and cursorPositionCoordinates() 0055 // random position 0056 view1->setCursorPosition(KTextEditor::Cursor(0, 3)); 0057 QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(view1->cursorPosition())), KTextEditor::Cursor(0, 3)); 0058 QCOMPARE(view1->coordinatesToCursor(view1->cursorPositionCoordinates()), KTextEditor::Cursor(0, 3)); 0059 // end of line 0060 view1->setCursorPosition(KTextEditor::Cursor(0, 9)); 0061 QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(0, 9))), KTextEditor::Cursor(0, 9)); 0062 QCOMPARE(view1->coordinatesToCursor(view1->cursorPositionCoordinates()), KTextEditor::Cursor(0, 9)); 0063 // empty line 0064 view1->setCursorPosition(KTextEditor::Cursor(2, 0)); 0065 QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(2, 0))), KTextEditor::Cursor(2, 0)); 0066 QCOMPARE(view1->coordinatesToCursor(view1->cursorPositionCoordinates()), KTextEditor::Cursor(2, 0)); 0067 0068 // same test again, but with message widget on top visible 0069 KTextEditor::Message *message = new KTextEditor::Message("Jo World!", KTextEditor::Message::Information); 0070 doc.postMessage(message); 0071 0072 // wait 500ms until show animation is finished, so the message widget is visible 0073 QTest::qWait(500); 0074 0075 QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(0, 2))), KTextEditor::Cursor(0, 2)); 0076 QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 1))), KTextEditor::Cursor(1, 1)); 0077 // behind end of line should give an invalid cursor 0078 QCOMPARE(view1->coordinatesToCursor(view1->cursorToCoordinate(KTextEditor::Cursor(1, 5))), KTextEditor::Cursor::invalid()); 0079 QCOMPARE(view1->cursorToCoordinate(KTextEditor::Cursor(3, 1)), QPoint(-1, -1)); 0080 } 0081 0082 void KateViewTest::testCursorToCoordinates() 0083 { 0084 KTextEditor::DocumentPrivate doc(false, false); 0085 doc.setText("int a;"); 0086 0087 KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); 0088 view->config()->setDynWordWrap(true); 0089 view->show(); 0090 0091 // don't crash, see https://bugs.kde.org/show_bug.cgi?id=337863 0092 view->cursorToCoordinate(Cursor(0, 0)); 0093 view->cursorToCoordinate(Cursor(1, 0)); 0094 view->cursorToCoordinate(Cursor(-1, 0)); 0095 } 0096 0097 void KateViewTest::testReloadMultipleViews() 0098 { 0099 QTemporaryFile file("XXXXXX.cpp"); 0100 file.open(); 0101 QTextStream stream(&file); 0102 const QString line = "const char* foo = \"asdf\"\n"; 0103 for (int i = 0; i < 200; ++i) { 0104 stream << line; 0105 } 0106 stream << Qt::flush; 0107 file.close(); 0108 0109 KTextEditor::DocumentPrivate doc; 0110 QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); 0111 QCOMPARE(doc.highlightingMode(), QString("C++")); 0112 0113 KTextEditor::ViewPrivate *view1 = new KTextEditor::ViewPrivate(&doc, nullptr); 0114 KTextEditor::ViewPrivate *view2 = new KTextEditor::ViewPrivate(&doc, nullptr); 0115 view1->show(); 0116 view2->show(); 0117 QCOMPARE(doc.views().count(), 2); 0118 0119 QVERIFY(doc.documentReload()); 0120 } 0121 0122 void KateViewTest::testTabCursorOnReload() 0123 { 0124 // testcase for https://bugs.kde.org/show_bug.cgi?id=258480 0125 QTemporaryFile file("XXXXXX.cpp"); 0126 file.open(); 0127 QTextStream stream(&file); 0128 stream << "\tfoo\n"; 0129 file.close(); 0130 0131 KTextEditor::DocumentPrivate doc; 0132 QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); 0133 0134 KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); 0135 const KTextEditor::Cursor cursor(0, 4); 0136 view->setCursorPosition(cursor); 0137 QCOMPARE(view->cursorPosition(), cursor); 0138 QVERIFY(doc.documentReload()); 0139 QCOMPARE(view->cursorPosition(), cursor); 0140 } 0141 0142 void KateViewTest::testLowerCaseBlockSelection() 0143 { 0144 // testcase for https://bugs.kde.org/show_bug.cgi?id=258480 0145 KTextEditor::DocumentPrivate doc; 0146 doc.setText("nY\nnYY\n"); 0147 0148 KTextEditor::ViewPrivate *view1 = new KTextEditor::ViewPrivate(&doc, nullptr); 0149 view1->setBlockSelection(true); 0150 view1->setSelection(Range(0, 1, 1, 3)); 0151 view1->lowercase(); 0152 0153 QCOMPARE(doc.text(), QString("ny\nnyy\n")); 0154 } 0155 0156 namespace 0157 { 0158 QWidget *findViewInternal(KTextEditor::View *view) 0159 { 0160 for (QObject *child : view->children()) { 0161 if (child->metaObject()->className() == QByteArrayLiteral("KateViewInternal")) { 0162 return qobject_cast<QWidget *>(child); 0163 } 0164 } 0165 return nullptr; 0166 } 0167 } 0168 0169 void KateViewTest::testSelection() 0170 { 0171 // see also: https://bugs.kde.org/show_bug.cgi?id=277422 0172 // wrong behavior before: 0173 // Open file with text 0174 // click at end of some line (A) and drag to right, i.e. without selecting anything 0175 // click somewhere else (B) 0176 // shift click to another place (C) 0177 // => expected: selection from B to C 0178 // => actual: selection from A to C 0179 0180 QTemporaryFile file("XXXXXX.txt"); 0181 file.open(); 0182 QTextStream stream(&file); 0183 stream << "A\n" 0184 << "B\n" 0185 << "C"; 0186 stream << Qt::flush; 0187 file.close(); 0188 0189 KTextEditor::DocumentPrivate doc; 0190 QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); 0191 0192 KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); 0193 view->resize(100, 200); 0194 view->show(); 0195 0196 QObject *internalView = findViewInternal(view); 0197 QVERIFY(internalView); 0198 0199 const QPoint afterA = view->cursorToCoordinate(Cursor(0, 1)); 0200 const QPoint afterB = view->cursorToCoordinate(Cursor(1, 1)); 0201 const QPoint afterC = view->cursorToCoordinate(Cursor(2, 1)); 0202 0203 // click after A 0204 auto me = QMouseEvent(QEvent::MouseButtonPress, afterA, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); 0205 QCoreApplication::sendEvent(internalView, &me); 0206 0207 auto me1 = QMouseEvent(QEvent::MouseButtonRelease, afterA, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); 0208 QCoreApplication::sendEvent(internalView, &me1); 0209 QCOMPARE(view->cursorPosition(), Cursor(0, 1)); 0210 0211 // drag to right 0212 auto me2 = QMouseEvent(QEvent::MouseButtonPress, afterA, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); 0213 QCoreApplication::sendEvent(internalView, &me2); 0214 0215 auto me3 = QMouseEvent(QEvent::MouseMove, afterA + QPoint(50, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); 0216 QCoreApplication::sendEvent(internalView, &me3); 0217 0218 auto me4 = QMouseEvent(QEvent::MouseButtonRelease, afterA + QPoint(50, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); 0219 QCoreApplication::sendEvent(internalView, &me4); 0220 0221 QCOMPARE(view->cursorPosition(), Cursor(0, 1)); 0222 QVERIFY(!view->selection()); 0223 0224 // click after C 0225 auto me5 = QMouseEvent(QEvent::MouseButtonPress, afterC, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); 0226 QCoreApplication::sendEvent(internalView, &me5); 0227 0228 auto me6 = QMouseEvent(QEvent::MouseButtonRelease, afterC, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); 0229 QCoreApplication::sendEvent(internalView, &me6); 0230 0231 QCOMPARE(view->cursorPosition(), Cursor(2, 1)); 0232 // shift+click after B 0233 auto me7 = QMouseEvent(QEvent::MouseButtonPress, afterB, Qt::LeftButton, Qt::LeftButton, Qt::ShiftModifier); 0234 QCoreApplication::sendEvent(internalView, &me7); 0235 0236 auto me8 = QMouseEvent(QEvent::MouseButtonRelease, afterB, Qt::LeftButton, Qt::LeftButton, Qt::ShiftModifier); 0237 QCoreApplication::sendEvent(internalView, &me8); 0238 0239 QCOMPARE(view->cursorPosition(), Cursor(1, 1)); 0240 QCOMPARE(view->selectionRange(), Range(1, 1, 2, 1)); 0241 } 0242 0243 void KateViewTest::testDeselectByArrowKeys_data() 0244 { 0245 QTest::addColumn<QString>("text"); 0246 0247 testNewRow() << "foobarhaz"; 0248 testNewRow() << "كلسشمن يتبكسب"; // We all win, translates Google 0249 } 0250 0251 void KateViewTest::testDeselectByArrowKeys() 0252 { 0253 QFETCH(QString, text); 0254 0255 KTextEditor::DocumentPrivate doc; 0256 doc.setText(text); 0257 KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); 0258 KTextEditor::Cursor cur1(0, 3); // Start of bar: foo|barhaz 0259 KTextEditor::Cursor cur2(0, 6); // End of bar: foobar|haz 0260 KTextEditor::Cursor curDelta(0, 1); 0261 Range range(cur1, cur2); // Select "bar" 0262 0263 // RTL drives me nuts! 0264 KTextEditor::Cursor help; 0265 if (text.isRightToLeft()) { 0266 help = cur1; 0267 cur1 = cur2; 0268 cur2 = help; 0269 } 0270 0271 view->setSelection(range); 0272 view->setCursorPositionInternal(cur1); 0273 view->cursorLeft(); 0274 QCOMPARE(view->cursorPosition(), cur1); // Be at begin: foo|barhaz 0275 QCOMPARE(view->selection(), false); 0276 0277 view->setSelection(range); 0278 view->setCursorPositionInternal(cur1); 0279 view->cursorRight(); 0280 QCOMPARE(view->cursorPosition(), cur2); // Be at end: foobar|haz 0281 QCOMPARE(view->selection(), false); 0282 0283 view->config()->setValue(KateViewConfig::PersistentSelection, true); 0284 0285 view->setSelection(range); 0286 view->setCursorPositionInternal(cur1); 0287 view->cursorLeft(); 0288 // RTL drives me nuts! 0289 help = text.isRightToLeft() ? (cur1 + curDelta) : (cur1 - curDelta); 0290 QCOMPARE(view->cursorPosition(), help); // Be one left: fo|obarhaz 0291 QCOMPARE(view->selection(), true); 0292 0293 view->setSelection(range); 0294 view->setCursorPositionInternal(cur1); 0295 view->cursorRight(); 0296 // RTL drives me nuts! 0297 help = text.isRightToLeft() ? (cur1 - curDelta) : (cur1 + curDelta); 0298 QCOMPARE(view->cursorPosition(), help); // Be one right: foob|arhaz 0299 QCOMPARE(view->selection(), true); 0300 } 0301 0302 void KateViewTest::testKillline() 0303 { 0304 KTextEditor::DocumentPrivate doc; 0305 doc.insertLines(0, {"foo", "bar", "baz"}); 0306 0307 KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); 0308 0309 view->setCursorPositionInternal(KTextEditor::Cursor(1, 2)); 0310 view->killLine(); 0311 0312 QCOMPARE(doc.text(), QLatin1String("foo\nbaz\n")); 0313 0314 doc.clear(); 0315 QVERIFY(doc.isEmpty()); 0316 0317 doc.insertLines(0, {"foo", "bar", "baz", "xxx"}); 0318 0319 view->setCursorPositionInternal(KTextEditor::Cursor(1, 2)); 0320 view->shiftDown(); 0321 view->killLine(); 0322 0323 QCOMPARE(doc.text(), QLatin1String("foo\nxxx\n")); 0324 } 0325 0326 void KateViewTest::testScrollPastEndOfDocument() 0327 { 0328 // bug 306745 0329 KTextEditor::DocumentPrivate doc; 0330 doc.setText( 0331 QStringLiteral("0000000000\n" 0332 "1111111111\n" 0333 "2222222222\n" 0334 "3333333333\n" 0335 "4444444444")); 0336 QCOMPARE(doc.lines(), 5); 0337 0338 KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); 0339 view->setCursorPosition({3, 5}); 0340 view->resize(400, 300); 0341 view->show(); 0342 0343 // enable "[x] Scroll past end of document" 0344 view->config()->setValue(KateViewConfig::ScrollPastEnd, true); 0345 QCOMPARE(view->config()->scrollPastEnd(), true); 0346 0347 // disable dynamic word wrap 0348 view->config()->setDynWordWrap(false); 0349 QCOMPARE(view->config()->dynWordWrap(), false); 0350 0351 view->scrollDown(); 0352 view->scrollDown(); 0353 view->scrollDown(); 0354 // at this point, only lines 3333333333 and 4444444444 are visible. 0355 view->down(); 0356 QCOMPARE(view->cursorPosition(), KTextEditor::Cursor(4, 5)); 0357 // verify, that only lines 3333333333 and 4444444444 are still visible. 0358 QCOMPARE(view->firstDisplayedLineInternal(KTextEditor::View::RealLine), 3); 0359 } 0360 0361 void KateViewTest::testFoldFirstLine() 0362 { 0363 QTemporaryFile file("XXXXXX.cpp"); 0364 file.open(); 0365 QTextStream stream(&file); 0366 stream << "/**\n" 0367 << " * foo\n" 0368 << " */\n" 0369 << "\n" 0370 << "int main() {}\n"; 0371 file.close(); 0372 0373 KTextEditor::DocumentPrivate doc; 0374 QVERIFY(doc.openUrl(QUrl::fromLocalFile(file.fileName()))); 0375 QCOMPARE(doc.highlightingMode(), QString("C++")); 0376 0377 KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); 0378 view->config()->setValue(KateViewConfig::FoldFirstLine, false); 0379 view->setCursorPosition({4, 0}); 0380 0381 // initially, nothing is folded 0382 QVERIFY(view->textFolding().isLineVisible(1)); 0383 0384 // now change the config, and expect the header to be folded 0385 view->config()->setValue(KateViewConfig::FoldFirstLine, true); 0386 qint64 foldedRangeId = 0; 0387 QVERIFY(!view->textFolding().isLineVisible(1, &foldedRangeId)); 0388 0389 // now unfold the range 0390 QVERIFY(view->textFolding().unfoldRange(foldedRangeId)); 0391 QVERIFY(view->textFolding().isLineVisible(1)); 0392 0393 // and save the file, we do not expect the folding to change then 0394 doc.setModified(true); 0395 doc.saveFile(); 0396 QVERIFY(view->textFolding().isLineVisible(1)); 0397 0398 // now reload the document, nothing should change 0399 doc.setModified(false); 0400 QVERIFY(doc.documentReload()); 0401 QVERIFY(view->textFolding().isLineVisible(1)); 0402 } 0403 0404 // test for bug https://bugs.kde.org/374163 0405 void KateViewTest::testDragAndDrop() 0406 { 0407 // ATM fails on Windows, mark as such to be able to enforce test success in CI 0408 #ifdef Q_OS_WIN 0409 QSKIP("Fails ATM, please fix"); 0410 #endif 0411 0412 KTextEditor::DocumentPrivate doc(false, false); 0413 doc.setText( 0414 "line0\n" 0415 "line1\n" 0416 "line2\n" 0417 "\n" 0418 "line4"); 0419 0420 KTextEditor::View *view = static_cast<KTextEditor::View *>(doc.createView(nullptr)); 0421 view->show(); 0422 view->resize(400, 300); 0423 0424 QWidget *internalView = findViewInternal(view); 0425 QVERIFY(internalView); 0426 0427 // select "line1\n" 0428 view->setSelection(Range(1, 0, 2, 0)); 0429 QCOMPARE(view->selectionRange(), Range(1, 0, 2, 0)); 0430 0431 (void)QTest::qWaitForWindowExposed(view); 0432 QTest::qWait(100); // For whatever reason needed 0433 0434 const QPoint startDragPos = internalView->mapFrom(view, view->cursorToCoordinate(KTextEditor::Cursor(1, 2))); 0435 const QPoint endDragPos = internalView->mapFrom(view, view->cursorToCoordinate(KTextEditor::Cursor(3, 0))); 0436 const QPoint gStartDragPos = internalView->mapToGlobal(startDragPos); 0437 const QPoint gEndDragPos = internalView->mapToGlobal(endDragPos); 0438 0439 // now drag and drop selected text to Cursor(3, 0) 0440 QMouseEvent pressEvent(QEvent::MouseButtonPress, startDragPos, gStartDragPos, Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); 0441 QCoreApplication::sendEvent(internalView, &pressEvent); 0442 0443 // ugly workaround: Drag & Drop has own blocking event queue. Therefore, we need a single-shot timer to 0444 // break out of the blocking event queue, see (*) 0445 QTimer::singleShot(50, [&]() { 0446 QMouseEvent moveEvent(QEvent::MouseMove, endDragPos + QPoint(5, 0), gEndDragPos + QPoint(5, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); 0447 QMouseEvent releaseEvent(QEvent::MouseButtonRelease, endDragPos, gEndDragPos, Qt::LeftButton, Qt::NoButton, Qt::NoModifier); 0448 QCoreApplication::sendEvent(internalView, &moveEvent); 0449 QCoreApplication::sendEvent(internalView, &releaseEvent); 0450 }); 0451 0452 // (*) this somehow blocks... 0453 QMouseEvent moveEvent1(QEvent::MouseMove, endDragPos + QPoint(10, 0), gEndDragPos + QPoint(10, 0), Qt::LeftButton, Qt::LeftButton, Qt::NoModifier); 0454 QCoreApplication::sendEvent(internalView, &moveEvent1); 0455 0456 QTest::qWait(100); 0457 0458 // final tests of dragged text 0459 QCOMPARE(doc.text(), 0460 QString("line0\n" 0461 "line2\n" 0462 "line1\n" 0463 "\n" 0464 "line4")); 0465 0466 QCOMPARE(view->cursorPosition(), KTextEditor::Cursor(3, 0)); 0467 QCOMPARE(view->selectionRange(), Range(2, 0, 3, 0)); 0468 } 0469 0470 // test for bug https://bugs.kde.org/402594 0471 void KateViewTest::testGotoMatchingBracket() 0472 { 0473 KTextEditor::DocumentPrivate doc(false, false); 0474 doc.setText("foo(bar)baz[]"); 0475 // 0123456789 0476 0477 KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); 0478 const KTextEditor::Cursor cursor1(0, 3); // Starting point on open ( 0479 const KTextEditor::Cursor cursor2(0, 8); // Insert Mode differ slightly from... 0480 const KTextEditor::Cursor cursor3(0, 7); // Overwrite Mode 0481 const KTextEditor::Cursor cursor4(0, 11); // Test adjacents brackets (start at [) 0482 0483 doc.config()->setOvr(false); // Insert Mode 0484 0485 view->setCursorPosition(cursor1); 0486 view->toMatchingBracket(); 0487 QCOMPARE(view->cursorPosition(), cursor2); 0488 view->toMatchingBracket(); 0489 QCOMPARE(view->cursorPosition(), cursor1); 0490 0491 // Currently has it in Insert Mode also to work when the cursor is placed inside the parentheses 0492 view->setCursorPosition(cursor1 + KTextEditor::Cursor(0, 1)); 0493 view->toMatchingBracket(); 0494 QCOMPARE(view->cursorPosition(), cursor2); 0495 view->setCursorPosition(cursor2 + KTextEditor::Cursor(0, -1)); 0496 view->toMatchingBracket(); 0497 QCOMPARE(view->cursorPosition(), cursor1); 0498 0499 view->setCursorPosition(cursor4 + KTextEditor::Cursor(0, 1)); 0500 view->toMatchingBracket(); 0501 QCOMPARE(view->cursorPosition(), cursor4); 0502 view->toMatchingBracket(); 0503 view->setCursorPosition(view->cursorPosition() + KTextEditor::Cursor(0, 1)); 0504 view->toMatchingBracket(); 0505 QCOMPARE(view->cursorPosition(), cursor4); 0506 0507 doc.config()->setOvr(true); // Overwrite Mode 0508 0509 view->setCursorPosition(cursor1); 0510 view->toMatchingBracket(); 0511 QCOMPARE(view->cursorPosition(), cursor3); 0512 view->toMatchingBracket(); 0513 QCOMPARE(view->cursorPosition(), cursor1); 0514 } 0515 0516 void KateViewTest::testFindSelected() 0517 { 0518 KTextEditor::DocumentPrivate doc(false, false); 0519 doc.setText( 0520 "foo\n" 0521 "bar\n" 0522 "foo\n" 0523 "bar\n"); 0524 // 0123456789 0525 0526 KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); 0527 const KTextEditor::Cursor cursor1(0, 0); // before 1. foo 0528 const KTextEditor::Cursor cursor2(0, 3); // after 1. foo 0529 const KTextEditor::Range range1({0, 0}, {0, 3}); // 1. foo range 0530 const KTextEditor::Cursor cursor3(2, 3); // after 2. foo 0531 const KTextEditor::Range range2({2, 0}, {2, 3}); // 2. foo range 0532 0533 QVERIFY(!view->selection()); 0534 QCOMPARE(view->cursorPosition(), cursor1); 0535 0536 // first time we call this without a selection, just select the word under the cursor 0537 view->findSelectedForwards(); 0538 QVERIFY(view->selection()); 0539 QCOMPARE(view->selectionRange(), range1); 0540 // cursor jumps to the end of the word 0541 QCOMPARE(view->cursorPosition(), cursor2); 0542 0543 // second time we call this it actually jumps to the next occurance 0544 view->findSelectedForwards(); 0545 QCOMPARE(view->selectionRange(), range2); 0546 QCOMPARE(view->cursorPosition(), cursor3); 0547 0548 // wrap around 0549 view->findSelectedForwards(); 0550 QCOMPARE(view->selectionRange(), range1); 0551 QCOMPARE(view->cursorPosition(), cursor2); 0552 0553 // search backwards, wrap again 0554 view->findSelectedBackwards(); 0555 QCOMPARE(view->selectionRange(), range2); 0556 QCOMPARE(view->cursorPosition(), cursor3); 0557 } 0558 0559 void KateViewTest::testTransposeWord() 0560 { 0561 KTextEditor::DocumentPrivate doc(false, false); 0562 doc.setText( 0563 "swaps forward\n" 0564 "wordAbove\n" 0565 "wordLeft (_skips]Spaces.__And___}Sym_bols)))) wordRight)\n" 0566 "wordBelow anotherWord yetAnotherWord\n"); 0567 0568 KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); 0569 const KTextEditor::Cursor swaps(0, 2); // swa|ps 0570 const KTextEditor::Cursor wordAbove(1, 4); // wordA|bove 0571 const KTextEditor::Cursor wordLeft(2, 1); // wo|rdLeft 0572 const KTextEditor::Cursor skips(2, 10); // |_skips 0573 const KTextEditor::Cursor And(2, 27); // __A|nd___ 0574 const KTextEditor::Cursor wordBelow(3, 0); // w|ordBelow 0575 0576 view->setCursorPosition(swaps); 0577 QCOMPARE(view->cursorPosition(), swaps); 0578 QCOMPARE(view->doc()->characterAt(view->cursorPosition()), QLatin1Char('a')); 0579 view->transposeWord(); 0580 QCOMPARE(view->cursorPosition(), swaps + KTextEditor::Cursor(0, 8)); // " forward" has 8 characters 0581 QCOMPARE(view->doc()->characterAt(view->cursorPosition()), QLatin1Char('a')); // retain relative position inside the word 0582 0583 view->transposeWord(); 0584 QCOMPARE(view->cursorPosition(), swaps); // when the word is already last in line, swap backwards instead 0585 0586 view->setCursorPosition(wordAbove); 0587 view->transposeWord(); 0588 QCOMPARE(view->cursorPosition(), wordAbove); // when there is no other word in the line, do nothing 0589 QCOMPARE(view->doc()->characterAt(view->cursorPosition()), QLatin1Char('A')); 0590 0591 view->setCursorPosition(wordLeft); 0592 view->transposeWord(); 0593 QCOMPARE(view->cursorPosition(), wordLeft); // when next word is invalid (made up of only symbols, in this case "(") do nothing 0594 QCOMPARE(view->doc()->characterAt(view->cursorPosition()), QLatin1Char('o')); 0595 0596 view->setCursorPosition(skips); 0597 view->transposeWord(); 0598 QCOMPARE(view->cursorPosition(), skips + KTextEditor::Cursor(0, 7)); // transpose word beginning with a symbol 0599 QCOMPARE(view->doc()->characterAt(view->cursorPosition()), QLatin1Char('_')); 0600 0601 view->setCursorPosition(And); 0602 view->transposeWord(); 0603 // skip multiple symbols if there is no space between current word and the symbols (in contrast to the case of wordLeft). 0604 // Can be useful for e.g. transposing function arguments 0605 QCOMPARE(view->cursorPosition(), And + KTextEditor::Cursor(0, 9)); 0606 0607 view->transposeWord(); 0608 QCOMPARE(view->cursorPosition(), And + KTextEditor::Cursor(0, 9 + 17)); // next "word" is invalid as it is only a single "(", so do nothing 0609 0610 view->setCursorPosition(wordBelow); 0611 view->transposeWord(); 0612 QCOMPARE(view->cursorPosition(), wordBelow + KTextEditor::Cursor(0, 12)); 0613 view->transposeWord(); 0614 QCOMPARE(view->cursorPosition(), wordBelow + KTextEditor::Cursor(0, 12 + 15)); 0615 view->transposeWord(); 0616 QCOMPARE(view->cursorPosition(), wordBelow + KTextEditor::Cursor(0, 12)); // end of line, transpose backwards instead 0617 } 0618 0619 void KateViewTest::testFindMatchingFoldingMarker() 0620 { 0621 KTextEditor::DocumentPrivate doc(false, false); 0622 doc.setMode(QString("Bash")); 0623 doc.setHighlightingMode(QString("Bash")); 0624 0625 doc.setText( 0626 "for i in 0 1 2; do\n" 0627 " if [[ i -lt 1 ]]; then echo $i; fi\n" 0628 " if [[ i -eq 2 ]]; then\n" 0629 " echo 'hello :)'\n" 0630 " fi\n" 0631 "done\n"); 0632 0633 KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); 0634 KateViewInternal *viewInternal = view->getViewInternal(); 0635 0636 const int ifvalue = doc.buffer().plainLine(1)->foldings()[0].foldingValue; 0637 const int dovalue = doc.buffer().plainLine(0)->foldings()[0].foldingValue; 0638 0639 const KTextEditor::Range firstDo(0, 16, 0, 18); 0640 const KTextEditor::Range firstDoMatching(5, 0, 5, 4); 0641 const KTextEditor::Range firstIf(1, 4, 1, 6); 0642 const KTextEditor::Range firstIfMatching(1, 36, 1, 38); 0643 0644 // first test the do folding marker with cursor above first position of the word. 0645 QCOMPARE(viewInternal->findMatchingFoldingMarker(firstDo.start(), dovalue, 2000), firstDoMatching); 0646 // with cursor above last position of the word 0647 QCOMPARE(viewInternal->findMatchingFoldingMarker(firstDo.end(), dovalue, 2000), firstDoMatching); 0648 // now to test the maxLines param. 0649 QCOMPARE(viewInternal->findMatchingFoldingMarker(firstDo.start(), dovalue, 2), KTextEditor::Range::invalid()); 0650 0651 // it must work from end folding to start folding too. 0652 QCOMPARE(viewInternal->findMatchingFoldingMarker(firstDoMatching.start(), -dovalue, 2000), firstDo); 0653 QCOMPARE(viewInternal->findMatchingFoldingMarker(firstDoMatching.start(), -dovalue, 2), KTextEditor::Range::invalid()); 0654 0655 // folding in the same line 0656 QCOMPARE(viewInternal->findMatchingFoldingMarker(firstIf.start(), ifvalue, 2000), firstIfMatching); 0657 QCOMPARE(viewInternal->findMatchingFoldingMarker(firstIfMatching.start(), -ifvalue, 2000), firstIf); 0658 } 0659 0660 void KateViewTest::testUpdateFoldingMarkersHighlighting() 0661 { 0662 KTextEditor::DocumentPrivate doc(false, false); 0663 doc.setMode(QString("Bash")); 0664 doc.setHighlightingMode(QString("Bash")); 0665 0666 doc.setText( 0667 "for i in 0 1 2; do\n" 0668 " if [[ i -lt 1 ]]; then echo $i; fi\n" 0669 " if [[ i -eq 2 ]]; then\n" 0670 " echo 'hello :)'\n" 0671 " fi\n" 0672 "done\n"); 0673 0674 KTextEditor::ViewPrivate *view = new KTextEditor::ViewPrivate(&doc, nullptr); 0675 KateViewInternal *viewInternal = view->getViewInternal(); 0676 0677 const KTextEditor::Cursor positionWithoutMarker(0, 4); 0678 const KTextEditor::Range firstDo(0, 16, 0, 18); 0679 const KTextEditor::Range firstDoMatching(5, 0, 5, 4); 0680 0681 KTextEditor::MovingRange *foldingMarkerStart = viewInternal->m_fmStart.get(); 0682 KTextEditor::MovingRange *foldingMarkerEnd = viewInternal->m_fmEnd.get(); 0683 0684 // If the cursor is not above any folding marker, the highlighting range is invalid 0685 view->editSetCursor(positionWithoutMarker); 0686 viewInternal->updateFoldingMarkersHighlighting(); 0687 QCOMPARE(foldingMarkerStart->toRange(), KTextEditor::Range::invalid()); 0688 QCOMPARE(foldingMarkerEnd->toRange(), KTextEditor::Range::invalid()); 0689 0690 // If the cursor is above a opening folding marker, the highlighting range is the range of both opening and end folding markers words 0691 view->editSetCursor(firstDo.start()); 0692 viewInternal->updateFoldingMarkersHighlighting(); 0693 QCOMPARE(foldingMarkerStart->toRange(), firstDo); 0694 QCOMPARE(foldingMarkerEnd->toRange(), firstDoMatching); 0695 0696 // If the cursor is above a ending folding marker, then same rule above 0697 view->editSetCursor(firstDoMatching.start()); 0698 viewInternal->updateFoldingMarkersHighlighting(); 0699 QCOMPARE(foldingMarkerStart->toRange(), firstDo); 0700 QCOMPARE(foldingMarkerEnd->toRange(), firstDoMatching); 0701 } 0702 0703 // kate: indent-mode cstyle; indent-width 4; replace-tabs on;