File indexing completed on 2024-05-19 15:18:49

0001 /*
0002     This file is part of the KDE libraries
0003     SPDX-FileCopyrightText: 2014 Miquel Sabaté Solà <mikisabate@gmail.com>
0004 
0005     SPDX-License-Identifier: LGPL-2.0-or-later
0006 */
0007 
0008 #include "base.h"
0009 #include "vimode/globalstate.h"
0010 #include "vimode/macros.h"
0011 #include "vimode/mappings.h"
0012 #include <inputmode/kateviinputmode.h>
0013 #include <kateconfig.h>
0014 #include <kateundomanager.h>
0015 #include <vimode/emulatedcommandbar/emulatedcommandbar.h>
0016 
0017 #include <katedocument.h>
0018 #include <kateview.h>
0019 
0020 using namespace KateVi;
0021 using namespace KTextEditor;
0022 
0023 // BEGIN: BaseTest
0024 
0025 BaseTest::BaseTest()
0026 {
0027     kate_view = nullptr;
0028     kate_document = nullptr;
0029 
0030     mainWindow = new QMainWindow;
0031     auto centralWidget = new QWidget();
0032     mainWindowLayout = new QVBoxLayout(centralWidget);
0033     mainWindow->setCentralWidget(centralWidget);
0034     mainWindow->resize(640, 480);
0035 
0036     m_codesToModifiers.insert("ctrl", CONTROL_MODIFIER);
0037     m_codesToModifiers.insert("alt", Qt::AltModifier);
0038     m_codesToModifiers.insert("meta", META_MODIFIER);
0039     m_codesToModifiers.insert("keypad", Qt::KeypadModifier);
0040 
0041     m_codesToSpecialKeys.insert("backspace", Qt::Key_Backspace);
0042     m_codesToSpecialKeys.insert("esc", Qt::Key_Escape);
0043     m_codesToSpecialKeys.insert("return", Qt::Key_Return);
0044     m_codesToSpecialKeys.insert("enter", Qt::Key_Enter);
0045     m_codesToSpecialKeys.insert("left", Qt::Key_Left);
0046     m_codesToSpecialKeys.insert("right", Qt::Key_Right);
0047     m_codesToSpecialKeys.insert("up", Qt::Key_Up);
0048     m_codesToSpecialKeys.insert("down", Qt::Key_Down);
0049     m_codesToSpecialKeys.insert("home", Qt::Key_Home);
0050     m_codesToSpecialKeys.insert("end", Qt::Key_End);
0051     m_codesToSpecialKeys.insert("delete", Qt::Key_Delete);
0052     m_codesToSpecialKeys.insert("insert", Qt::Key_Insert);
0053     m_codesToSpecialKeys.insert("pageup", Qt::Key_PageUp);
0054     m_codesToSpecialKeys.insert("pagedown", Qt::Key_PageDown);
0055 }
0056 
0057 BaseTest::~BaseTest()
0058 {
0059     delete kate_document;
0060 }
0061 
0062 void BaseTest::waitForCompletionWidgetToActivate(KTextEditor::ViewPrivate *kate_view)
0063 {
0064     const QDateTime start = QDateTime::currentDateTime();
0065     while (start.msecsTo(QDateTime::currentDateTime()) < 1000) {
0066         if (kate_view->isCompletionActive()) {
0067             break;
0068         }
0069         QApplication::processEvents();
0070     }
0071     QVERIFY(kate_view->isCompletionActive());
0072 }
0073 
0074 void BaseTest::init()
0075 {
0076     delete kate_view;
0077     delete kate_document;
0078 
0079     kate_document = new KTextEditor::DocumentPrivate();
0080 
0081     // fixed indentation options
0082     kate_document->config()->setTabWidth(8);
0083     kate_document->config()->setIndentationWidth(2);
0084     kate_document->config()->setReplaceTabsDyn(false);
0085 
0086     kate_view = new KTextEditor::ViewPrivate(kate_document, mainWindow);
0087     mainWindowLayout->addWidget(kate_view);
0088     kate_view->config()->setValue(KateViewConfig::AutoBrackets, false);
0089     kate_view->setInputMode(View::ViInputMode);
0090     Q_ASSERT(kate_view->currentInputMode()->viewInputMode() == KTextEditor::View::ViInputMode);
0091     vi_input_mode = dynamic_cast<KateViInputMode *>(kate_view->currentInputMode());
0092     vi_input_mode_manager = vi_input_mode->viInputModeManager();
0093     Q_ASSERT(vi_input_mode_manager);
0094     vi_global = vi_input_mode->globalState();
0095     Q_ASSERT(vi_global);
0096     kate_document->config()->setShowSpaces(KateDocumentConfig::Trailing); // Flush out some issues in the KateRenderer when rendering spaces.
0097     kate_view->config()->setValue(KateViewConfig::ShowScrollBarMiniMap, false);
0098     kate_view->config()->setValue(KateViewConfig::ShowScrollBarPreview, false);
0099 
0100     connect(kate_document, &KTextEditor::DocumentPrivate::textInsertedRange, this, &BaseTest::textInserted);
0101     connect(kate_document, &KTextEditor::DocumentPrivate::textRemoved, this, &BaseTest::textRemoved);
0102 }
0103 
0104 void BaseTest::TestPressKey(const QString &str)
0105 {
0106     if (m_firstBatchOfKeypressesForTest) {
0107         qDebug() << "\n\n>>> running command " << str << " on text " << kate_document->text();
0108     } else {
0109         qDebug() << "\n>>> running further keypresses " << str << " on text " << kate_document->text();
0110     }
0111     m_firstBatchOfKeypressesForTest = false;
0112 
0113     for (int i = 0; i < str.length(); i++) {
0114         Qt::KeyboardModifiers keyboard_modifier = Qt::NoModifier;
0115         QString key;
0116         int keyCode = -1;
0117         // Looking for keyboard modifiers
0118         if (str[i] == QChar('\\')) {
0119             int endOfModifier = -1;
0120             Qt::KeyboardModifier parsedModifier = parseCodedModifier(str, i, &endOfModifier);
0121             int endOfSpecialKey = -1;
0122             Qt::Key parsedSpecialKey = parseCodedSpecialKey(str, i, &endOfSpecialKey);
0123             if (parsedModifier != Qt::NoModifier) {
0124                 keyboard_modifier = parsedModifier;
0125                 // Move to the character after the '-' in the modifier.
0126                 i = endOfModifier + 1;
0127                 // Is this a modifier plus special key?
0128                 int endOfSpecialKeyAfterModifier = -1;
0129                 const Qt::Key parsedCodedSpecialKeyAfterModifier = parseCodedSpecialKey(str, i, &endOfSpecialKeyAfterModifier);
0130                 if (parsedCodedSpecialKeyAfterModifier != Qt::Key_unknown) {
0131                     key = parsedCodedSpecialKeyAfterModifier <= 0xffff ? QString(QChar(parsedCodedSpecialKeyAfterModifier)) : QString();
0132                     keyCode = parsedCodedSpecialKeyAfterModifier;
0133                     i = endOfSpecialKeyAfterModifier;
0134                 }
0135             } else if (parsedSpecialKey != Qt::Key_unknown) {
0136                 key = parsedSpecialKey <= 0xffff ? QString(QChar(parsedSpecialKey)) : QString();
0137                 keyCode = parsedSpecialKey;
0138                 i = endOfSpecialKey;
0139             } else if (str.mid(i, 2) == QString("\\:")) {
0140                 int start_cmd = i + 2;
0141                 for (i += 2; true; i++) {
0142                     if (str.at(i) == '\\') {
0143                         if (i + 1 < str.length() && str.at(i + 1) == '\\') {
0144                             // A backslash within a command; skip.
0145                             i += 2;
0146                         } else {
0147                             // End of command.
0148                             break;
0149                         }
0150                     }
0151                 }
0152                 const QString commandToExecute = str.mid(start_cmd, i - start_cmd).replace("\\\\", "\\");
0153                 qDebug() << "Executing command directly from ViModeTest:\n" << commandToExecute;
0154                 vi_input_mode->viModeEmulatedCommandBar()->executeCommand(commandToExecute);
0155                 // We've handled the command; go back round the loop, avoiding sending
0156                 // the closing \ to vi_input_mode_manager.
0157                 continue;
0158             } else if (str.mid(i, 2) == QString("\\\\")) {
0159                 key = QString("\\");
0160                 keyCode = Qt::Key_Backslash;
0161                 i++;
0162             } else {
0163                 Q_ASSERT(false); // Do not use "\" in tests except for modifiers, command mode (\\:) and literal backslashes "\\\\")
0164             }
0165         }
0166 
0167         if (keyCode == -1) {
0168             key = str[i];
0169             keyCode = key[0].unicode();
0170             // Kate Vim mode's internals identifier e.g. CTRL-C by Qt::Key_C plus the control modifier,
0171             // so we need to translate e.g. 'c' to Key_C.
0172             if (key[0].isLetter()) {
0173                 if (key[0].toLower() == key[0]) {
0174                     keyCode = keyCode - 'a' + Qt::Key_A;
0175                 } else {
0176                     keyCode = keyCode - 'A' + Qt::Key_A;
0177                     keyboard_modifier |= Qt::ShiftModifier;
0178                 }
0179             }
0180         }
0181 
0182         QKeyEvent key_event(QEvent::KeyPress, keyCode, keyboard_modifier, key);
0183         // Attempt to simulate how Qt usually sends events - typically, we want to send them
0184         // to kate_view->focusProxy() (which is a KateViewInternal).
0185         QWidget *destWidget = nullptr;
0186         if (QApplication::activePopupWidget()) {
0187             // According to the docs, the activePopupWidget, if present, takes all events.
0188             destWidget = QApplication::activePopupWidget();
0189         } else if (QApplication::focusWidget()) {
0190             if (QApplication::focusWidget()->focusProxy()) {
0191                 destWidget = QApplication::focusWidget()->focusProxy();
0192             } else {
0193                 destWidget = QApplication::focusWidget();
0194             }
0195         } else {
0196             destWidget = kate_view->focusProxy();
0197         }
0198         QApplication::sendEvent(destWidget, &key_event);
0199     }
0200 }
0201 
0202 void BaseTest::BeginTest(const QString &original)
0203 {
0204     vi_input_mode->viInputModeManager()->viEnterNormalMode();
0205     vi_input_mode->reset();
0206     vi_input_mode_manager = vi_input_mode->viInputModeManager();
0207     kate_document->setText(original);
0208     kate_document->undoManager()->clearUndo();
0209     kate_document->undoManager()->clearRedo();
0210     kate_view->setCursorPosition(Cursor(0, 0));
0211     m_firstBatchOfKeypressesForTest = true;
0212 }
0213 
0214 void BaseTest::FinishTest_(int line, const char *file, const QString &expected, Expectation expectation, const QString &failureReason)
0215 {
0216     if (expectation == ShouldFail) {
0217         if (!QTest::qExpectFail("", failureReason.toLocal8Bit().constData(), QTest::Continue, file, line)) {
0218             return;
0219         }
0220         qDebug() << "Actual text:\n\t" << kate_document->text() << "\nShould be (for this test to pass):\n\t" << expected;
0221     }
0222     if (!QTest::qCompare(kate_document->text(), expected, "kate_document->text()", "expected_text", file, line)) {
0223         return;
0224     }
0225     Q_ASSERT(!emulatedCommandBarTextEdit()->isVisible() && "Make sure you close the command bar before the end of a test!");
0226 }
0227 
0228 void BaseTest::DoTest_(int line,
0229                        const char *file,
0230                        const QString &original,
0231                        const QString &command,
0232                        const QString &expected,
0233                        Expectation expectation,
0234                        const QString &failureReason)
0235 {
0236     BeginTest(original);
0237     TestPressKey(command);
0238     FinishTest_(line, file, expected, expectation, failureReason);
0239 }
0240 
0241 Qt::KeyboardModifier BaseTest::parseCodedModifier(const QString &string, int startPos, int *destEndOfCodedModifier)
0242 {
0243     for (auto it = m_codesToModifiers.constBegin(), end = m_codesToModifiers.constEnd(); it != end; ++it) {
0244         const QString &modifierCode = it.key();
0245         // The "+2" is from the leading '\' and the trailing '-'
0246         if (string.mid(startPos, modifierCode.length() + 2) == QString("\\") + modifierCode + "-") {
0247             if (destEndOfCodedModifier) {
0248                 // destEndOfCodeModifier lies on the trailing '-'.
0249                 *destEndOfCodedModifier = startPos + modifierCode.length() + 1;
0250                 Q_ASSERT(string[*destEndOfCodedModifier] == '-');
0251             }
0252             return it.value();
0253         }
0254     }
0255     return Qt::NoModifier;
0256 }
0257 
0258 Qt::Key BaseTest::parseCodedSpecialKey(const QString &string, int startPos, int *destEndOfCodedKey)
0259 {
0260     for (auto it = m_codesToSpecialKeys.constBegin(), end = m_codesToSpecialKeys.constEnd(); it != end; ++it) {
0261         const QString &specialKeyCode = it.key();
0262         // "+1" is for the leading '\'.
0263         if (string.mid(startPos, specialKeyCode.length() + 1) == QString("\\") + specialKeyCode) {
0264             if (destEndOfCodedKey) {
0265                 *destEndOfCodedKey = startPos + specialKeyCode.length();
0266             }
0267             return it.value();
0268         }
0269     }
0270     return Qt::Key_unknown;
0271 }
0272 
0273 KateVi::EmulatedCommandBar *BaseTest::emulatedCommandBar()
0274 {
0275     KateVi::EmulatedCommandBar *emulatedCommandBar = vi_input_mode->viModeEmulatedCommandBar();
0276     Q_ASSERT(emulatedCommandBar);
0277     return emulatedCommandBar;
0278 }
0279 
0280 QLineEdit *BaseTest::emulatedCommandBarTextEdit()
0281 {
0282     QLineEdit *emulatedCommandBarText = emulatedCommandBar()->findChild<QLineEdit *>("commandtext");
0283     Q_ASSERT(emulatedCommandBarText);
0284     return emulatedCommandBarText;
0285 }
0286 
0287 void BaseTest::ensureKateViewVisible()
0288 {
0289     mainWindow->show();
0290     kate_view->show();
0291     QApplication::setActiveWindow(mainWindow);
0292     kate_view->setFocus();
0293     const QDateTime startTime = QDateTime::currentDateTime();
0294     while (startTime.msecsTo(QDateTime::currentDateTime()) < 3000 && !mainWindow->isActiveWindow()) {
0295         QApplication::processEvents();
0296     }
0297     QVERIFY(kate_view->isVisible());
0298     QVERIFY(mainWindow->isActiveWindow());
0299 }
0300 
0301 void BaseTest::clearAllMappings()
0302 {
0303     vi_global->mappings()->clear(Mappings::NormalModeMapping);
0304     vi_global->mappings()->clear(Mappings::VisualModeMapping);
0305     vi_global->mappings()->clear(Mappings::InsertModeMapping);
0306     vi_global->mappings()->clear(Mappings::CommandModeMapping);
0307 }
0308 
0309 void BaseTest::clearAllMacros()
0310 {
0311     vi_global->macros()->clear();
0312 }
0313 
0314 void BaseTest::textInserted(Document *document, KTextEditor::Range range)
0315 {
0316     m_docChanges.append(DocChange(DocChange::TextInserted, range, document->text(range)));
0317 }
0318 
0319 void BaseTest::textRemoved(Document *document, KTextEditor::Range range)
0320 {
0321     Q_UNUSED(document);
0322     m_docChanges.append(DocChange(DocChange::TextRemoved, range));
0323 }
0324 
0325 // END: BaseTest
0326 
0327 #include "moc_base.cpp"