File indexing completed on 2024-04-21 03:57:12

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