Warning, file /frameworks/ktexteditor/src/vimode/emulatedcommandbar/emulatedcommandbar.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).
0001 /* 0002 SPDX-FileCopyrightText: 2013-2016 Simon St James <kdedevel@etotheipiplusone.com> 0003 0004 SPDX-License-Identifier: LGPL-2.0-or-later 0005 */ 0006 0007 #include <vimode/emulatedcommandbar/emulatedcommandbar.h> 0008 0009 #include "../commandrangeexpressionparser.h" 0010 #include "../globalstate.h" 0011 #include "commandmode.h" 0012 #include "interactivesedreplacemode.h" 0013 #include "katedocument.h" 0014 #include "kateglobal.h" 0015 #include "kateview.h" 0016 #include "matchhighlighter.h" 0017 #include "searchmode.h" 0018 #include <inputmode/kateviinputmode.h> 0019 #include <vimode/inputmodemanager.h> 0020 #include <vimode/keyparser.h> 0021 #include <vimode/modes/normalvimode.h> 0022 0023 #include "../history.h" 0024 0025 #include "../registers.h" 0026 0027 #include <QApplication> 0028 #include <QLabel> 0029 #include <QLineEdit> 0030 #include <QVBoxLayout> 0031 0032 #include <algorithm> 0033 0034 using namespace KateVi; 0035 0036 namespace 0037 { 0038 /** 0039 * @return \a originalRegex but escaped in such a way that a Qt regex search for 0040 * the resulting string will match the string \a originalRegex. 0041 */ 0042 QString escapedForSearchingAsLiteral(const QString &originalQtRegex) 0043 { 0044 QString escapedForSearchingAsLiteral = originalQtRegex; 0045 escapedForSearchingAsLiteral.replace(QLatin1Char('\\'), QLatin1String("\\\\")); 0046 escapedForSearchingAsLiteral.replace(QLatin1Char('$'), QLatin1String("\\$")); 0047 escapedForSearchingAsLiteral.replace(QLatin1Char('^'), QLatin1String("\\^")); 0048 escapedForSearchingAsLiteral.replace(QLatin1Char('.'), QLatin1String("\\.")); 0049 escapedForSearchingAsLiteral.replace(QLatin1Char('*'), QLatin1String("\\*")); 0050 escapedForSearchingAsLiteral.replace(QLatin1Char('/'), QLatin1String("\\/")); 0051 escapedForSearchingAsLiteral.replace(QLatin1Char('['), QLatin1String("\\[")); 0052 escapedForSearchingAsLiteral.replace(QLatin1Char(']'), QLatin1String("\\]")); 0053 escapedForSearchingAsLiteral.replace(QLatin1Char('\n'), QLatin1String("\\n")); 0054 return escapedForSearchingAsLiteral; 0055 } 0056 } 0057 0058 EmulatedCommandBar::EmulatedCommandBar(KateViInputMode *viInputMode, InputModeManager *viInputModeManager, QWidget *parent) 0059 : KateViewBarWidget(false, parent) 0060 , m_viInputMode(viInputMode) 0061 , m_viInputModeManager(viInputModeManager) 0062 , m_view(viInputModeManager->view()) 0063 { 0064 QHBoxLayout *layout = new QHBoxLayout(centralWidget()); 0065 layout->setContentsMargins(0, 0, 0, 0); 0066 0067 createAndAddBarTypeIndicator(layout); 0068 createAndAddEditWidget(layout); 0069 createAndAddExitStatusMessageDisplay(layout); 0070 createAndInitExitStatusMessageDisplayTimer(); 0071 createAndAddWaitingForRegisterIndicator(layout); 0072 0073 m_matchHighligher.reset(new MatchHighlighter(m_view)); 0074 0075 m_completer.reset(new Completer(this, m_view, m_edit)); 0076 0077 m_interactiveSedReplaceMode.reset(new InteractiveSedReplaceMode(this, m_matchHighligher.data(), m_viInputModeManager, m_view)); 0078 layout->addWidget(m_interactiveSedReplaceMode->label()); 0079 m_searchMode.reset(new SearchMode(this, m_matchHighligher.data(), m_viInputModeManager, m_view, m_edit)); 0080 m_commandMode.reset( 0081 new CommandMode(this, m_matchHighligher.data(), m_viInputModeManager, m_view, m_edit, m_interactiveSedReplaceMode.data(), m_completer.data())); 0082 0083 m_edit->installEventFilter(this); 0084 connect(m_edit, &QLineEdit::textChanged, this, &EmulatedCommandBar::editTextChanged); 0085 } 0086 0087 EmulatedCommandBar::~EmulatedCommandBar() = default; 0088 0089 void EmulatedCommandBar::init(EmulatedCommandBar::Mode mode, const QString &initialText) 0090 { 0091 m_mode = mode; 0092 m_isActive = true; 0093 m_wasAborted = true; 0094 0095 showBarTypeIndicator(mode); 0096 0097 if (mode == KateVi::EmulatedCommandBar::SearchBackward || mode == SearchForward) { 0098 switchToMode(m_searchMode.data()); 0099 m_searchMode->init(mode == SearchBackward ? SearchMode::SearchDirection::Backward : SearchMode::SearchDirection::Forward); 0100 } else { 0101 switchToMode(m_commandMode.data()); 0102 } 0103 0104 m_edit->setFocus(); 0105 m_edit->setText(initialText); 0106 m_edit->show(); 0107 0108 m_exitStatusMessageDisplay->hide(); 0109 m_exitStatusMessageDisplayHideTimer->stop(); 0110 0111 // A change in focus will have occurred: make sure we process it now, instead of having it 0112 // occur later and stop() m_commandResponseMessageDisplayHide. 0113 // This is generally only a problem when feeding a sequence of keys without human intervention, 0114 // as when we execute a mapping, macro, or test case. 0115 QApplication::processEvents(); 0116 } 0117 0118 bool EmulatedCommandBar::isActive() const 0119 { 0120 return m_isActive; 0121 } 0122 0123 void EmulatedCommandBar::setCommandResponseMessageTimeout(long int commandResponseMessageTimeOutMS) 0124 { 0125 m_exitStatusMessageHideTimeOutMS = commandResponseMessageTimeOutMS; 0126 } 0127 0128 void EmulatedCommandBar::closed() 0129 { 0130 m_matchHighligher->updateMatchHighlight(KTextEditor::Range::invalid()); 0131 m_completer->deactivateCompletion(); 0132 m_isActive = false; 0133 0134 if (m_currentMode) { 0135 m_currentMode->deactivate(m_wasAborted); 0136 m_currentMode = nullptr; 0137 } 0138 } 0139 0140 void EmulatedCommandBar::switchToMode(ActiveMode *newMode) 0141 { 0142 if (newMode == m_currentMode) { 0143 return; 0144 } 0145 if (m_currentMode) { 0146 m_currentMode->deactivate(false); 0147 } 0148 m_currentMode = newMode; 0149 m_completer->setCurrentMode(newMode); 0150 } 0151 0152 bool EmulatedCommandBar::barHandledKeypress(const QKeyEvent *keyEvent) 0153 { 0154 if ((keyEvent->modifiers() == CONTROL_MODIFIER && keyEvent->key() == Qt::Key_H) || keyEvent->key() == Qt::Key_Backspace) { 0155 if (m_edit->text().isEmpty()) { 0156 Q_EMIT hideMe(); 0157 } 0158 m_edit->backspace(); 0159 return true; 0160 } 0161 if (keyEvent->modifiers() != CONTROL_MODIFIER) { 0162 return false; 0163 } 0164 if (keyEvent->key() == Qt::Key_B) { 0165 m_edit->setCursorPosition(0); 0166 return true; 0167 } else if (keyEvent->key() == Qt::Key_E) { 0168 m_edit->setCursorPosition(m_edit->text().length()); 0169 return true; 0170 } else if (keyEvent->key() == Qt::Key_W) { 0171 deleteSpacesToLeftOfCursor(); 0172 if (!deleteNonWordCharsToLeftOfCursor()) { 0173 deleteWordCharsToLeftOfCursor(); 0174 } 0175 return true; 0176 } else if (keyEvent->key() == Qt::Key_R || keyEvent->key() == Qt::Key_G) { 0177 m_waitingForRegister = true; 0178 m_waitingForRegisterIndicator->setVisible(true); 0179 if (keyEvent->key() == Qt::Key_G) { 0180 m_insertedTextShouldBeEscapedForSearchingAsLiteral = true; 0181 } 0182 return true; 0183 } 0184 return false; 0185 } 0186 0187 void EmulatedCommandBar::insertRegisterContents(const QKeyEvent *keyEvent) 0188 { 0189 if (keyEvent->key() != Qt::Key_Shift && keyEvent->key() != Qt::Key_Control) { 0190 const QChar key = KeyParser::self()->KeyEventToQChar(*keyEvent).toLower(); 0191 0192 const int oldCursorPosition = m_edit->cursorPosition(); 0193 QString textToInsert; 0194 if (keyEvent->modifiers() == CONTROL_MODIFIER && keyEvent->key() == Qt::Key_W) { 0195 textToInsert = m_view->doc()->wordAt(m_view->cursorPosition()); 0196 } else { 0197 textToInsert = m_viInputModeManager->globalState()->registers()->getContent(key); 0198 } 0199 if (m_insertedTextShouldBeEscapedForSearchingAsLiteral) { 0200 textToInsert = escapedForSearchingAsLiteral(textToInsert); 0201 m_insertedTextShouldBeEscapedForSearchingAsLiteral = false; 0202 } 0203 m_edit->setText(m_edit->text().insert(m_edit->cursorPosition(), textToInsert)); 0204 m_edit->setCursorPosition(oldCursorPosition + textToInsert.length()); 0205 m_waitingForRegister = false; 0206 m_waitingForRegisterIndicator->setVisible(false); 0207 } 0208 } 0209 0210 bool EmulatedCommandBar::eventFilter(QObject *object, QEvent *event) 0211 { 0212 // The "object" will be either m_edit or m_completer's popup. 0213 if (m_suspendEditEventFiltering) { 0214 return false; 0215 } 0216 Q_UNUSED(object); 0217 if (event->type() == QEvent::KeyPress) { 0218 // Re-route this keypress through Vim's central keypress handling area, so that we can use the keypress in e.g. 0219 // mappings and macros. 0220 return m_viInputMode->keyPress(static_cast<QKeyEvent *>(event)); 0221 } 0222 return false; 0223 } 0224 0225 void EmulatedCommandBar::deleteSpacesToLeftOfCursor() 0226 { 0227 while (m_edit->cursorPosition() != 0 && m_edit->text().at(m_edit->cursorPosition() - 1) == QLatin1Char(' ')) { 0228 m_edit->backspace(); 0229 } 0230 } 0231 0232 void EmulatedCommandBar::deleteWordCharsToLeftOfCursor() 0233 { 0234 while (m_edit->cursorPosition() != 0) { 0235 const QChar charToTheLeftOfCursor = m_edit->text().at(m_edit->cursorPosition() - 1); 0236 if (!charToTheLeftOfCursor.isLetterOrNumber() && charToTheLeftOfCursor != QLatin1Char('_')) { 0237 break; 0238 } 0239 0240 m_edit->backspace(); 0241 } 0242 } 0243 0244 bool EmulatedCommandBar::deleteNonWordCharsToLeftOfCursor() 0245 { 0246 bool deletionsMade = false; 0247 while (m_edit->cursorPosition() != 0) { 0248 const QChar charToTheLeftOfCursor = m_edit->text().at(m_edit->cursorPosition() - 1); 0249 if (charToTheLeftOfCursor.isLetterOrNumber() || charToTheLeftOfCursor == QLatin1Char('_') || charToTheLeftOfCursor == QLatin1Char(' ')) { 0250 break; 0251 } 0252 0253 m_edit->backspace(); 0254 deletionsMade = true; 0255 } 0256 return deletionsMade; 0257 } 0258 0259 bool EmulatedCommandBar::handleKeyPress(const QKeyEvent *keyEvent) 0260 { 0261 if (m_waitingForRegister) { 0262 insertRegisterContents(keyEvent); 0263 return true; 0264 } 0265 const bool completerHandled = m_completer->completerHandledKeypress(keyEvent); 0266 if (completerHandled) { 0267 return true; 0268 } 0269 0270 if (keyEvent->modifiers() == CONTROL_MODIFIER && (keyEvent->key() == Qt::Key_C || keyEvent->key() == Qt::Key_BracketLeft)) { 0271 Q_EMIT hideMe(); 0272 return true; 0273 } 0274 0275 // Is this a built-in Emulated Command Bar keypress e.g. insert from register, ctrl-h, etc? 0276 const bool barHandled = barHandledKeypress(keyEvent); 0277 if (barHandled) { 0278 return true; 0279 } 0280 0281 // Can the current mode handle it? 0282 const bool currentModeHandled = m_currentMode->handleKeyPress(keyEvent); 0283 if (currentModeHandled) { 0284 return true; 0285 } 0286 0287 // Couldn't handle this key event. 0288 // Send the keypress back to the QLineEdit. Ideally, instead of doing this, we would simply return "false" 0289 // and let Qt re-dispatch the event itself; however, there is a corner case in that if the selection 0290 // changes (as a result of e.g. incremental searches during Visual Mode), and the keypress that causes it 0291 // is not dispatched from within KateViInputModeHandler::handleKeypress(...) 0292 // (so KateViInputModeManager::isHandlingKeypress() returns false), we lose information about whether we are 0293 // in Visual Mode, Visual Line Mode, etc. See VisualViMode::updateSelection( ). 0294 if (m_edit->isVisible()) { 0295 if (m_suspendEditEventFiltering) { 0296 return false; 0297 } 0298 m_suspendEditEventFiltering = true; 0299 QKeyEvent keyEventCopy(keyEvent->type(), keyEvent->key(), keyEvent->modifiers(), keyEvent->text(), keyEvent->isAutoRepeat(), keyEvent->count()); 0300 qApp->notify(m_edit, &keyEventCopy); 0301 m_suspendEditEventFiltering = false; 0302 } 0303 return true; 0304 } 0305 0306 bool EmulatedCommandBar::isSendingSyntheticSearchCompletedKeypress() 0307 { 0308 return m_searchMode->isSendingSyntheticSearchCompletedKeypress(); 0309 } 0310 0311 void EmulatedCommandBar::startInteractiveSearchAndReplace(QSharedPointer<SedReplace::InteractiveSedReplacer> interactiveSedReplace) 0312 { 0313 Q_ASSERT_X(interactiveSedReplace->currentMatch().isValid(), 0314 "startInteractiveSearchAndReplace", 0315 "KateCommands shouldn't initiate an interactive sed replace with no initial match"); 0316 switchToMode(m_interactiveSedReplaceMode.data()); 0317 m_interactiveSedReplaceMode->activate(interactiveSedReplace); 0318 } 0319 0320 void EmulatedCommandBar::showBarTypeIndicator(EmulatedCommandBar::Mode mode) 0321 { 0322 QChar barTypeIndicator = QChar::Null; 0323 switch (mode) { 0324 case SearchForward: 0325 barTypeIndicator = QLatin1Char('/'); 0326 break; 0327 case SearchBackward: 0328 barTypeIndicator = QLatin1Char('?'); 0329 break; 0330 case Command: 0331 barTypeIndicator = QLatin1Char(':'); 0332 break; 0333 default: 0334 Q_ASSERT(false && "Unknown mode!"); 0335 } 0336 m_barTypeIndicator->setText(barTypeIndicator); 0337 m_barTypeIndicator->show(); 0338 } 0339 0340 QString EmulatedCommandBar::executeCommand(const QString &commandToExecute) 0341 { 0342 return m_commandMode->executeCommand(commandToExecute); 0343 } 0344 0345 void EmulatedCommandBar::closeWithStatusMessage(const QString &exitStatusMessage) 0346 { 0347 // Display the message for a while. Become inactive, so we don't steal keys in the meantime. 0348 m_isActive = false; 0349 0350 m_exitStatusMessageDisplay->show(); 0351 m_exitStatusMessageDisplay->setText(exitStatusMessage); 0352 hideAllWidgetsExcept(m_exitStatusMessageDisplay); 0353 0354 m_exitStatusMessageDisplayHideTimer->start(m_exitStatusMessageHideTimeOutMS); 0355 } 0356 0357 void EmulatedCommandBar::editTextChanged(const QString &newText) 0358 { 0359 Q_ASSERT(!m_interactiveSedReplaceMode->isActive()); 0360 m_currentMode->editTextChanged(newText); 0361 m_completer->editTextChanged(newText); 0362 } 0363 0364 void EmulatedCommandBar::startHideExitStatusMessageTimer() 0365 { 0366 if (m_exitStatusMessageDisplay->isVisible() && !m_exitStatusMessageDisplayHideTimer->isActive()) { 0367 m_exitStatusMessageDisplayHideTimer->start(m_exitStatusMessageHideTimeOutMS); 0368 } 0369 } 0370 0371 void EmulatedCommandBar::setViInputModeManager(InputModeManager *viInputModeManager) 0372 { 0373 m_viInputModeManager = viInputModeManager; 0374 m_searchMode->setViInputModeManager(viInputModeManager); 0375 m_commandMode->setViInputModeManager(viInputModeManager); 0376 m_interactiveSedReplaceMode->setViInputModeManager(viInputModeManager); 0377 } 0378 0379 void EmulatedCommandBar::hideAllWidgetsExcept(QWidget *widgetToKeepVisible) 0380 { 0381 const QList<QWidget *> widgets = centralWidget()->findChildren<QWidget *>(); 0382 for (QWidget *widget : widgets) { 0383 if (widget != widgetToKeepVisible) { 0384 widget->hide(); 0385 } 0386 } 0387 } 0388 0389 void EmulatedCommandBar::createAndAddBarTypeIndicator(QLayout *layout) 0390 { 0391 m_barTypeIndicator = new QLabel(this); 0392 m_barTypeIndicator->setObjectName(QStringLiteral("bartypeindicator")); 0393 layout->addWidget(m_barTypeIndicator); 0394 } 0395 0396 void EmulatedCommandBar::createAndAddEditWidget(QLayout *layout) 0397 { 0398 m_edit = new QLineEdit(this); 0399 m_edit->setObjectName(QStringLiteral("commandtext")); 0400 layout->addWidget(m_edit); 0401 } 0402 0403 void EmulatedCommandBar::createAndAddExitStatusMessageDisplay(QLayout *layout) 0404 { 0405 m_exitStatusMessageDisplay = new QLabel(this); 0406 m_exitStatusMessageDisplay->setObjectName(QStringLiteral("commandresponsemessage")); 0407 m_exitStatusMessageDisplay->setAlignment(Qt::AlignLeft); 0408 layout->addWidget(m_exitStatusMessageDisplay); 0409 } 0410 0411 void EmulatedCommandBar::createAndInitExitStatusMessageDisplayTimer() 0412 { 0413 m_exitStatusMessageDisplayHideTimer = new QTimer(this); 0414 m_exitStatusMessageDisplayHideTimer->setSingleShot(true); 0415 connect(m_exitStatusMessageDisplayHideTimer, &QTimer::timeout, this, &EmulatedCommandBar::hideMe); 0416 // Make sure the timer is stopped when the user switches views. If not, focus will be given to the 0417 // wrong view when KateViewBar::hideCurrentBarWidget() is called as a result of m_commandResponseMessageDisplayHide 0418 // timing out. 0419 connect(m_view, &KTextEditor::ViewPrivate::focusOut, m_exitStatusMessageDisplayHideTimer, &QTimer::stop); 0420 // We can restart the timer once the view has focus again, though. 0421 connect(m_view, &KTextEditor::ViewPrivate::focusIn, this, &EmulatedCommandBar::startHideExitStatusMessageTimer); 0422 } 0423 0424 void EmulatedCommandBar::createAndAddWaitingForRegisterIndicator(QLayout *layout) 0425 { 0426 m_waitingForRegisterIndicator = new QLabel(this); 0427 m_waitingForRegisterIndicator->setObjectName(QStringLiteral("waitingforregisterindicator")); 0428 m_waitingForRegisterIndicator->setVisible(false); 0429 m_waitingForRegisterIndicator->setText(QStringLiteral("\"")); 0430 layout->addWidget(m_waitingForRegisterIndicator); 0431 } 0432 0433 #include "moc_emulatedcommandbar.cpp"