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"