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;