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