File indexing completed on 2024-05-12 11:58:34

0001 /*
0002     SPDX-FileCopyrightText: 2008-2009 Erlend Hamberg <ehamberg@gmail.com>
0003     SPDX-FileCopyrightText: 2008 Evgeniy Ivanov <powerfox@kde.ru>
0004     SPDX-FileCopyrightText: 2009 Paul Gideon Dann <pdgiddie@gmail.com>
0005     SPDX-FileCopyrightText: 2011 Svyatoslav Kuzmich <svatoslav1@gmail.com>
0006     SPDX-FileCopyrightText: 2012-2013 Simon St James <kdedevel@etotheipiplusone.com>
0007 
0008     SPDX-License-Identifier: LGPL-2.0-or-later
0009 */
0010 
0011 #include "katebuffer.h"
0012 #include "katecmd.h"
0013 #include "katecompletionwidget.h"
0014 #include "kateconfig.h"
0015 #include "katedocument.h"
0016 #include "kateglobal.h"
0017 #include "katepartdebug.h"
0018 #include "kateundomanager.h"
0019 #include "kateviewhelpers.h"
0020 #include "kateviewinternal.h"
0021 #include "kateviinputmode.h"
0022 #include <ktexteditor/attribute.h>
0023 #include <vimode/emulatedcommandbar/emulatedcommandbar.h>
0024 #include <vimode/globalstate.h>
0025 #include <vimode/history.h>
0026 #include <vimode/inputmodemanager.h>
0027 #include <vimode/keymapper.h>
0028 #include <vimode/keyparser.h>
0029 #include <vimode/lastchangerecorder.h>
0030 #include <vimode/macrorecorder.h>
0031 #include <vimode/marks.h>
0032 #include <vimode/modes/insertvimode.h>
0033 #include <vimode/modes/normalvimode.h>
0034 #include <vimode/modes/replacevimode.h>
0035 #include <vimode/modes/visualvimode.h>
0036 #include <vimode/registers.h>
0037 #include <vimode/searcher.h>
0038 
0039 #include <KLocalizedString>
0040 #include <QApplication>
0041 #include <QList>
0042 
0043 using namespace KateVi;
0044 
0045 NormalViMode::NormalViMode(InputModeManager *viInputModeManager, KTextEditor::ViewPrivate *view, KateViewInternal *viewInternal)
0046     : ModeBase()
0047 {
0048     m_view = view;
0049     m_viewInternal = viewInternal;
0050     m_viInputModeManager = viInputModeManager;
0051     m_stickyColumn = -1;
0052     m_lastMotionWasVisualLineUpOrDown = false;
0053     m_currentMotionWasVisualLineUpOrDown = false;
0054 
0055     // FIXME: make configurable
0056     m_extraWordCharacters = QString();
0057     m_matchingItems[QStringLiteral("/*")] = QStringLiteral("*/");
0058     m_matchingItems[QStringLiteral("*/")] = QStringLiteral("-/*");
0059 
0060     m_matchItemRegex = generateMatchingItemRegex();
0061 
0062     m_scroll_count_limit = 1000; // Limit of count for scroll commands.
0063 
0064     m_pendingResetIsDueToExit = false;
0065     m_isRepeatedTFcommand = false;
0066     m_lastMotionWasLinewiseInnerBlock = false;
0067     m_motionCanChangeWholeVisualModeSelection = false;
0068     resetParser(); // initialise with start configuration
0069 
0070     m_isUndo = false;
0071     connect(doc()->undoManager(), &KateUndoManager::undoStart, this, &NormalViMode::undoBeginning);
0072     connect(doc()->undoManager(), &KateUndoManager::undoEnd, this, &NormalViMode::undoEnded);
0073 
0074     updateYankHighlightAttrib();
0075     connect(view, &KTextEditor::View::configChanged, this, &NormalViMode::updateYankHighlightAttrib);
0076     connect(doc(), &KTextEditor::DocumentPrivate::aboutToInvalidateMovingInterfaceContent, this, &NormalViMode::clearYankHighlight);
0077     connect(doc(), &KTextEditor::DocumentPrivate::aboutToDeleteMovingInterfaceContent, this, &NormalViMode::aboutToDeleteMovingInterfaceContent);
0078 }
0079 
0080 NormalViMode::~NormalViMode()
0081 {
0082     qDeleteAll(m_highlightedYanks);
0083 }
0084 
0085 /**
0086  * parses a key stroke to check if it's a valid (part of) a command
0087  * @return true if a command was completed and executed, false otherwise
0088  */
0089 bool NormalViMode::handleKeypress(const QKeyEvent *e)
0090 {
0091     const int keyCode = e->key();
0092 
0093     // ignore modifier keys alone
0094     if (keyCode == Qt::Key_Shift || keyCode == Qt::Key_Control || keyCode == Qt::Key_Alt || keyCode == Qt::Key_Meta) {
0095         return false;
0096     }
0097 
0098     clearYankHighlight();
0099 
0100     if (keyCode == Qt::Key_Escape || (keyCode == Qt::Key_C && e->modifiers() == CONTROL_MODIFIER)
0101         || (keyCode == Qt::Key_BracketLeft && e->modifiers() == CONTROL_MODIFIER)) {
0102         m_viInputModeManager->inputAdapter()->setCaretStyle(KateRenderer::Block);
0103         m_pendingResetIsDueToExit = true;
0104         // Vim in weird as if we e.g. i<ctrl-o><ctrl-c> it claims (in the status bar) to still be in insert mode,
0105         // but behaves as if it's in normal mode. I'm treating the status bar thing as a bug and just exiting
0106         // insert mode altogether.
0107         m_viInputModeManager->setTemporaryNormalMode(false);
0108         reset();
0109         return true;
0110     }
0111 
0112     const QChar key = KeyParser::self()->KeyEventToQChar(*e);
0113 
0114     const QChar lastChar = m_keys.isEmpty() ? QChar::Null : m_keys.at(m_keys.size() - 1);
0115     const bool waitingForRegisterOrCharToSearch = this->waitingForRegisterOrCharToSearch();
0116 
0117     // Use replace caret when reading a character for "r"
0118     if (key == QLatin1Char('r') && !waitingForRegisterOrCharToSearch) {
0119         m_viInputModeManager->inputAdapter()->setCaretStyle(KateRenderer::Underline);
0120     }
0121 
0122     m_keysVerbatim.append(KeyParser::self()->decodeKeySequence(key));
0123 
0124     if ((keyCode >= Qt::Key_0 && keyCode <= Qt::Key_9 && lastChar != QLatin1Char('"')) // key 0-9
0125         && (m_countTemp != 0 || keyCode != Qt::Key_0) // first digit can't be 0
0126         && (!waitingForRegisterOrCharToSearch) // Not in the middle of "find char" motions or replacing char.
0127         && e->modifiers() == Qt::NoModifier) {
0128         m_countTemp *= 10;
0129         m_countTemp += keyCode - Qt::Key_0;
0130 
0131         return true;
0132     } else if (m_countTemp != 0) {
0133         m_count = getCount() * m_countTemp;
0134         m_countTemp = 0;
0135         m_iscounted = true;
0136     }
0137 
0138     m_keys.append(key);
0139 
0140     if (m_viInputModeManager->macroRecorder()->isRecording() && key == QLatin1Char('q')) {
0141         // Need to special case this "finish macro" q, as the "begin macro" q
0142         // needs a parameter whereas the finish macro does not.
0143         m_viInputModeManager->macroRecorder()->stop();
0144         resetParser();
0145         return true;
0146     }
0147 
0148     if ((key == QLatin1Char('/') || key == QLatin1Char('?')) && !waitingForRegisterOrCharToSearch) {
0149         // Special case for "/" and "?": these should be motions, but this is complicated by
0150         // the fact that the user must interact with the search bar before the range of the
0151         // motion can be determined.
0152         // We hack around this by showing the search bar immediately, and, when the user has
0153         // finished interacting with it, have the search bar send a "synthetic" keypresses
0154         // that will either abort everything (if the search was aborted) or "complete" the motion
0155         // otherwise.
0156         m_positionWhenIncrementalSearchBegan = m_view->cursorPosition();
0157         if (key == QLatin1Char('/')) {
0158             commandSearchForward();
0159         } else {
0160             commandSearchBackward();
0161         }
0162         return true;
0163     }
0164 
0165     // Special case: "cw" and "cW" work the same as "ce" and "cE" if the cursor is
0166     // on a non-blank.  This is because Vim interprets "cw" as change-word, and a
0167     // word does not include the following white space. (:help cw in vim)
0168     if ((m_keys == QLatin1String("cw") || m_keys == QLatin1String("cW")) && !getCharUnderCursor().isSpace()) {
0169         // Special case of the special case: :-)
0170         // If the cursor is at the end of the current word rewrite to "cl"
0171         const bool isWORD = (m_keys.at(1) == QLatin1Char('W'));
0172         const KTextEditor::Cursor currentPosition(m_view->cursorPosition());
0173         const KTextEditor::Cursor endOfWordOrWORD = (isWORD ? findWORDEnd(currentPosition.line(), currentPosition.column() - 1, true)
0174                                                             : findWordEnd(currentPosition.line(), currentPosition.column() - 1, true));
0175 
0176         if (currentPosition == endOfWordOrWORD) {
0177             m_keys = QStringLiteral("cl");
0178         } else {
0179             if (isWORD) {
0180                 m_keys = QStringLiteral("cE");
0181             } else {
0182                 m_keys = QStringLiteral("ce");
0183             }
0184         }
0185     }
0186 
0187     if (m_keys[0] == QChar(Qt::Key_QuoteDbl)) {
0188         if (m_keys.size() < 2) {
0189             return true; // waiting for a register
0190         } else {
0191             QChar r = m_keys[1].toLower();
0192 
0193             if ((r >= QLatin1Char('0') && r <= QLatin1Char('9')) || (r >= QLatin1Char('a') && r <= QLatin1Char('z')) || r == QLatin1Char('_')
0194                 || r == QLatin1Char('-') || r == QLatin1Char('+') || r == QLatin1Char('*') || r == QLatin1Char('#') || r == QLatin1Char('^')) {
0195                 m_register = m_keys[1];
0196                 m_keys.clear();
0197                 return true;
0198             } else {
0199                 resetParser();
0200                 return true;
0201             }
0202         }
0203     }
0204 
0205     // if we have any matching commands so far, check which ones still match
0206     if (!m_matchingCommands.isEmpty()) {
0207         int n = m_matchingCommands.size() - 1;
0208 
0209         // remove commands not matching anymore
0210         for (int i = n; i >= 0; i--) {
0211             if (!commands().at(m_matchingCommands.at(i)).matches(m_keys)) {
0212                 if (commands().at(m_matchingCommands.at(i)).needsMotion()) {
0213                     // "cache" command needing a motion for later
0214                     m_motionOperatorIndex = m_matchingCommands.at(i);
0215                 }
0216                 m_matchingCommands.remove(i);
0217             }
0218         }
0219 
0220         // check if any of the matching commands need a motion/text object, if so
0221         // push the current command length to m_awaitingMotionOrTextObject so one
0222         // knows where to split the command between the operator and the motion
0223         for (int i = 0; i < m_matchingCommands.size(); i++) {
0224             if (commands().at(m_matchingCommands.at(i)).needsMotion()) {
0225                 m_awaitingMotionOrTextObject.push(m_keys.size());
0226                 break;
0227             }
0228         }
0229     } else {
0230         // go through all registered commands and put possible matches in m_matchingCommands
0231         for (size_t i = 0; i < commands().size(); i++) {
0232             if (commands().at(i).matches(m_keys)) {
0233                 m_matchingCommands.push_back(i);
0234                 if (commands().at(i).needsMotion() && commands().at(i).pattern().length() == m_keys.size()) {
0235                     m_awaitingMotionOrTextObject.push(m_keys.size());
0236                 }
0237             }
0238         }
0239     }
0240 
0241     // this indicates where in the command string one should start looking for a motion command
0242     int checkFrom = (m_awaitingMotionOrTextObject.isEmpty() ? 0 : m_awaitingMotionOrTextObject.top());
0243 
0244     // Use operator-pending caret when reading a motion for an operator
0245     // in normal mode. We need to check that we are indeed in normal mode
0246     // since visual mode inherits from it.
0247     if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode && !m_awaitingMotionOrTextObject.isEmpty()) {
0248         m_viInputModeManager->inputAdapter()->setCaretStyle(KateRenderer::Half);
0249     }
0250 
0251     // look for matching motion commands from position 'checkFrom'
0252     // FIXME: if checkFrom hasn't changed, only motions whose index is in
0253     // m_matchingMotions should be checked
0254     bool motionExecuted = false;
0255     if (checkFrom < m_keys.size()) {
0256         for (size_t i = 0; i < motions().size(); i++) {
0257             const QString motion = m_keys.mid(checkFrom);
0258             if (motions().at(i).matches(motion)) {
0259                 m_lastMotionWasLinewiseInnerBlock = false;
0260                 m_matchingMotions.push_back(i);
0261 
0262                 // if it matches exact, we have found the motion command to execute
0263                 if (motions().at(i).matchesExact(motion)) {
0264                     m_currentMotionWasVisualLineUpOrDown = false;
0265                     motionExecuted = true;
0266                     if (checkFrom == 0) {
0267                         // no command given before motion, just move the cursor to wherever
0268                         // the motion says it should go to
0269                         Range r = motions().at(i).execute(this);
0270                         m_motionCanChangeWholeVisualModeSelection = motions().at(i).canChangeWholeVisualModeSelection();
0271 
0272                         if (!motions().at(i).canLandInsideFoldingRange()) {
0273                             // jump over folding regions since we are just moving the cursor
0274                             // except for motions that can end up inside ranges (e.g. n/N, f/F, %, #)
0275                             int currLine = m_view->cursorPosition().line();
0276                             int delta = r.endLine - currLine;
0277                             int vline = m_view->textFolding().lineToVisibleLine(currLine);
0278                             r.endLine = m_view->textFolding().visibleLineToLine(qMax(vline + delta, 0) /* ensure we have a valid line */);
0279                         }
0280 
0281                         if (r.endLine >= doc()->lines()) {
0282                             r.endLine = doc()->lines() - 1;
0283                         }
0284 
0285                         // make sure the position is valid before moving the cursor there
0286                         // TODO: can this be simplified? :/
0287                         if (r.valid && r.endLine >= 0 && (r.endLine == 0 || r.endLine <= doc()->lines() - 1) && r.endColumn >= 0) {
0288                             if (r.endColumn >= doc()->lineLength(r.endLine) && doc()->lineLength(r.endLine) > 0) {
0289                                 r.endColumn = doc()->lineLength(r.endLine) - 1;
0290                             }
0291 
0292                             goToPos(r);
0293 
0294                             // in the case of VisualMode we need to remember the motion commands as well.
0295                             if (!m_viInputModeManager->isAnyVisualMode()) {
0296                                 m_viInputModeManager->clearCurrentChangeLog();
0297                             }
0298                         } else {
0299                             qCDebug(LOG_KTE) << "Invalid position: (" << r.endLine << "," << r.endColumn << ")";
0300                         }
0301 
0302                         resetParser();
0303 
0304                         // if normal mode was started by using Ctrl-O in insert mode,
0305                         // it's time to go back to insert mode.
0306                         if (m_viInputModeManager->getTemporaryNormalMode()) {
0307                             startInsertMode();
0308                             m_viewInternal->repaint();
0309                         }
0310 
0311                         m_lastMotionWasVisualLineUpOrDown = m_currentMotionWasVisualLineUpOrDown;
0312 
0313                         break;
0314                     } else {
0315                         // execute the specified command and supply the position returned from
0316                         // the motion
0317 
0318                         m_commandRange = motions().at(i).execute(this);
0319                         m_linewiseCommand = motions().at(i).isLineWise();
0320 
0321                         // if we didn't get an explicit start position, use the current cursor position
0322                         if (m_commandRange.startLine == -1) {
0323                             KTextEditor::Cursor c(m_view->cursorPosition());
0324                             m_commandRange.startLine = c.line();
0325                             m_commandRange.startColumn = c.column();
0326                         }
0327 
0328                         // special case: When using the "w" motion in combination with an operator and
0329                         // the last word moved over is at the end of a line, the end of that word
0330                         // becomes the end of the operated text, not the first word in the next line.
0331                         if (motions().at(i).pattern() == QLatin1String("w") || motions().at(i).pattern() == QLatin1String("W")) {
0332                             if (m_commandRange.endLine != m_commandRange.startLine && m_commandRange.endColumn == getFirstNonBlank(m_commandRange.endLine)) {
0333                                 m_commandRange.endLine--;
0334                                 m_commandRange.endColumn = doc()->lineLength(m_commandRange.endLine);
0335                             }
0336                         }
0337 
0338                         m_commandWithMotion = true;
0339 
0340                         if (m_commandRange.valid) {
0341                             executeCommand(&commands().at(m_motionOperatorIndex));
0342                         } else {
0343                             qCDebug(LOG_KTE) << "Invalid range: "
0344                                              << "from (" << m_commandRange.startLine << "," << m_commandRange.startColumn << ")"
0345                                              << "to (" << m_commandRange.endLine << "," << m_commandRange.endColumn << ")";
0346                         }
0347 
0348                         if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) {
0349                             m_viInputModeManager->inputAdapter()->setCaretStyle(KateRenderer::Block);
0350                         }
0351                         m_commandWithMotion = false;
0352                         reset();
0353                         break;
0354                     }
0355                 }
0356             }
0357         }
0358     }
0359 
0360     if (this->waitingForRegisterOrCharToSearch()) {
0361         // If we are waiting for a char to search or a new register,
0362         // don't translate next character; we need the actual character so that e.g.
0363         // 'ab' is translated to 'fb' if the mappings 'a' -> 'f' and 'b' -> something else
0364         // exist.
0365         m_viInputModeManager->keyMapper()->setDoNotMapNextKeypress();
0366     }
0367 
0368     if (motionExecuted) {
0369         return true;
0370     }
0371 
0372     // if we have only one match, check if it is a perfect match and if so, execute it
0373     // if it's not waiting for a motion or a text object
0374     if (m_matchingCommands.size() == 1) {
0375         if (commands().at(m_matchingCommands.at(0)).matchesExact(m_keys) && !commands().at(m_matchingCommands.at(0)).needsMotion()) {
0376             if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) {
0377                 m_viInputModeManager->inputAdapter()->setCaretStyle(KateRenderer::Block);
0378             }
0379 
0380             const Command &cmd = commands().at(m_matchingCommands.at(0));
0381             executeCommand(&cmd);
0382 
0383             // check if reset() should be called. some commands in visual mode should not end visual mode
0384             if (cmd.shouldReset()) {
0385                 reset();
0386                 m_view->setBlockSelection(false);
0387             }
0388             resetParser();
0389 
0390             return true;
0391         }
0392     } else if (m_matchingCommands.size() == 0 && m_matchingMotions.size() == 0) {
0393         resetParser();
0394         // A bit ugly:  we haven't made use of the key event,
0395         // but don't want "typeable" keypresses (e.g. a, b, 3, etc) to be marked
0396         // as unused as they will then be added to the document, but we don't
0397         // want to swallow all keys in case this was a shortcut.
0398         // So say we made use of it if and only if it was *not* a shortcut.
0399         return e->type() != QEvent::ShortcutOverride;
0400     }
0401 
0402     m_matchingMotions.clear();
0403     return true; // TODO - need to check this - it's currently required for making tests pass, but seems odd.
0404 }
0405 
0406 /**
0407  * (re)set to start configuration. This is done when a command is completed
0408  * executed or when a command is aborted
0409  */
0410 void NormalViMode::resetParser()
0411 {
0412     m_keys.clear();
0413     m_keysVerbatim.clear();
0414     m_count = 0;
0415     m_oneTimeCountOverride = -1;
0416     m_iscounted = false;
0417     m_countTemp = 0;
0418     m_register = QChar::Null;
0419     m_findWaitingForChar = false;
0420     m_matchingCommands.clear();
0421     m_matchingMotions.clear();
0422     m_awaitingMotionOrTextObject.clear();
0423     m_motionOperatorIndex = 0;
0424 
0425     m_commandWithMotion = false;
0426     m_linewiseCommand = true;
0427     m_deleteCommand = false;
0428 
0429     m_commandShouldKeepSelection = false;
0430 
0431     m_currentChangeEndMarker = KTextEditor::Cursor::invalid();
0432 
0433     if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) {
0434         m_viInputModeManager->inputAdapter()->setCaretStyle(KateRenderer::Block);
0435     }
0436 }
0437 
0438 // reset the command parser
0439 void NormalViMode::reset()
0440 {
0441     resetParser();
0442     m_commandRange.startLine = -1;
0443     m_commandRange.startColumn = -1;
0444 }
0445 
0446 void NormalViMode::beginMonitoringDocumentChanges()
0447 {
0448     connect(doc(), &KTextEditor::DocumentPrivate::textInsertedRange, this, &NormalViMode::textInserted);
0449     connect(doc(), &KTextEditor::DocumentPrivate::textRemoved, this, &NormalViMode::textRemoved);
0450 }
0451 
0452 void NormalViMode::executeCommand(const Command *cmd)
0453 {
0454     const ViMode originalViMode = m_viInputModeManager->getCurrentViMode();
0455 
0456     cmd->execute(this);
0457 
0458     // if normal mode was started by using Ctrl-O in insert mode,
0459     // it's time to go back to insert mode.
0460     if (m_viInputModeManager->getTemporaryNormalMode()) {
0461         startInsertMode();
0462         m_viewInternal->repaint();
0463     }
0464 
0465     // if the command was a change, and it didn't enter insert mode, store the key presses so that
0466     // they can be repeated with '.'
0467     if (m_viInputModeManager->getCurrentViMode() != ViMode::InsertMode && m_viInputModeManager->getCurrentViMode() != ViMode::ReplaceMode) {
0468         if (cmd->isChange() && !m_viInputModeManager->lastChangeRecorder()->isReplaying()) {
0469             m_viInputModeManager->storeLastChangeCommand();
0470         }
0471 
0472         // when we transition to visual mode, remember the command in the keys history (V, v, ctrl-v, ...)
0473         // this will later result in buffer filled with something like this "Vjj>" which we can use later with repeat "."
0474         const bool commandSwitchedToVisualMode = ((originalViMode == ViMode::NormalMode) && m_viInputModeManager->isAnyVisualMode());
0475         if (!commandSwitchedToVisualMode) {
0476             m_viInputModeManager->clearCurrentChangeLog();
0477         }
0478     }
0479 
0480     // make sure the cursor does not end up after the end of the line
0481     KTextEditor::Cursor c(m_view->cursorPosition());
0482     if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) {
0483         int lineLength = doc()->lineLength(c.line());
0484 
0485         if (c.column() >= lineLength) {
0486             if (lineLength == 0) {
0487                 c.setColumn(0);
0488             } else {
0489                 c.setColumn(lineLength - 1);
0490             }
0491         }
0492         updateCursor(c);
0493     }
0494 }
0495 
0496 ////////////////////////////////////////////////////////////////////////////////
0497 // COMMANDS AND OPERATORS
0498 ////////////////////////////////////////////////////////////////////////////////
0499 
0500 /**
0501  * enter insert mode at the cursor position
0502  */
0503 
0504 bool NormalViMode::commandEnterInsertMode()
0505 {
0506     m_stickyColumn = -1;
0507     m_viInputModeManager->getViInsertMode()->setCount(getCount());
0508     return startInsertMode();
0509 }
0510 
0511 /**
0512  * enter insert mode after the current character
0513  */
0514 
0515 bool NormalViMode::commandEnterInsertModeAppend()
0516 {
0517     KTextEditor::Cursor c(m_view->cursorPosition());
0518     c.setColumn(c.column() + 1);
0519 
0520     // if empty line, the cursor should start at column 0
0521     if (doc()->lineLength(c.line()) == 0) {
0522         c.setColumn(0);
0523     }
0524 
0525     // cursor should never be in a column > number of columns
0526     if (c.column() > doc()->lineLength(c.line())) {
0527         c.setColumn(doc()->lineLength(c.line()));
0528     }
0529 
0530     updateCursor(c);
0531 
0532     m_stickyColumn = -1;
0533     m_viInputModeManager->getViInsertMode()->setCount(getCount());
0534     return startInsertMode();
0535 }
0536 
0537 /**
0538  * start insert mode after the last character of the line
0539  */
0540 
0541 bool NormalViMode::commandEnterInsertModeAppendEOL()
0542 {
0543     KTextEditor::Cursor c(m_view->cursorPosition());
0544     c.setColumn(doc()->lineLength(c.line()));
0545     updateCursor(c);
0546 
0547     m_stickyColumn = -1;
0548     m_viInputModeManager->getViInsertMode()->setCount(getCount());
0549     return startInsertMode();
0550 }
0551 
0552 bool NormalViMode::commandEnterInsertModeBeforeFirstNonBlankInLine()
0553 {
0554     KTextEditor::Cursor cursor(m_view->cursorPosition());
0555     int c = getFirstNonBlank();
0556 
0557     cursor.setColumn(c);
0558     updateCursor(cursor);
0559 
0560     m_stickyColumn = -1;
0561     m_viInputModeManager->getViInsertMode()->setCount(getCount());
0562     return startInsertMode();
0563 }
0564 
0565 /**
0566  * enter insert mode at the last insert position
0567  */
0568 
0569 bool NormalViMode::commandEnterInsertModeLast()
0570 {
0571     KTextEditor::Cursor c = m_viInputModeManager->marks()->getInsertStopped();
0572     if (c.isValid()) {
0573         updateCursor(c);
0574     }
0575 
0576     m_stickyColumn = -1;
0577     return startInsertMode();
0578 }
0579 
0580 bool NormalViMode::commandEnterVisualLineMode()
0581 {
0582     if (m_viInputModeManager->getCurrentViMode() == VisualLineMode) {
0583         reset();
0584         return true;
0585     }
0586 
0587     return startVisualLineMode();
0588 }
0589 
0590 bool NormalViMode::commandEnterVisualBlockMode()
0591 {
0592     if (m_viInputModeManager->getCurrentViMode() == VisualBlockMode) {
0593         reset();
0594         return true;
0595     }
0596 
0597     return startVisualBlockMode();
0598 }
0599 
0600 bool NormalViMode::commandReselectVisual()
0601 {
0602     // start last visual mode and set start = `< and cursor = `>
0603     KTextEditor::Cursor c1 = m_viInputModeManager->marks()->getSelectionStart();
0604     KTextEditor::Cursor c2 = m_viInputModeManager->marks()->getSelectionFinish();
0605 
0606     // we should either get two valid cursors or two invalid cursors
0607     Q_ASSERT(c1.isValid() == c2.isValid());
0608 
0609     if (c1.isValid() && c2.isValid()) {
0610         m_viInputModeManager->getViVisualMode()->setStart(c1);
0611         bool returnValue = false;
0612 
0613         switch (m_viInputModeManager->getViVisualMode()->getLastVisualMode()) {
0614         case ViMode::VisualMode:
0615             returnValue = commandEnterVisualMode();
0616             break;
0617         case ViMode::VisualLineMode:
0618             returnValue = commandEnterVisualLineMode();
0619             break;
0620         case ViMode::VisualBlockMode:
0621             returnValue = commandEnterVisualBlockMode();
0622             break;
0623         default:
0624             Q_ASSERT("invalid visual mode");
0625         }
0626         m_viInputModeManager->getViVisualMode()->goToPos(c2);
0627         return returnValue;
0628     } else {
0629         error(QStringLiteral("No previous visual selection"));
0630     }
0631 
0632     return false;
0633 }
0634 
0635 bool NormalViMode::commandEnterVisualMode()
0636 {
0637     if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualMode) {
0638         reset();
0639         return true;
0640     }
0641 
0642     return startVisualMode();
0643 }
0644 
0645 bool NormalViMode::commandToOtherEnd()
0646 {
0647     if (m_viInputModeManager->isAnyVisualMode()) {
0648         m_viInputModeManager->getViVisualMode()->switchStartEnd();
0649         return true;
0650     }
0651 
0652     return false;
0653 }
0654 
0655 bool NormalViMode::commandEnterReplaceMode()
0656 {
0657     m_stickyColumn = -1;
0658     m_viInputModeManager->getViReplaceMode()->setCount(getCount());
0659     return startReplaceMode();
0660 }
0661 
0662 bool NormalViMode::commandDeleteLine()
0663 {
0664     KTextEditor::Cursor c(m_view->cursorPosition());
0665 
0666     Range r;
0667 
0668     r.startLine = c.line();
0669     r.endLine = c.line() + getCount() - 1;
0670 
0671     int column = c.column();
0672 
0673     bool ret = deleteRange(r, LineWise);
0674 
0675     c = m_view->cursorPosition();
0676     if (column > doc()->lineLength(c.line()) - 1) {
0677         column = doc()->lineLength(c.line()) - 1;
0678     }
0679     if (column < 0) {
0680         column = 0;
0681     }
0682 
0683     if (c.line() > doc()->lines() - 1) {
0684         c.setLine(doc()->lines() - 1);
0685     }
0686 
0687     c.setColumn(column);
0688     m_stickyColumn = -1;
0689     updateCursor(c);
0690 
0691     m_deleteCommand = true;
0692     return ret;
0693 }
0694 
0695 bool NormalViMode::commandDelete()
0696 {
0697     m_deleteCommand = true;
0698     return deleteRange(m_commandRange, getOperationMode());
0699 }
0700 
0701 bool NormalViMode::commandDeleteToEOL()
0702 {
0703     KTextEditor::Cursor c(m_view->cursorPosition());
0704     OperationMode m = CharWise;
0705 
0706     m_commandRange.endColumn = KateVi::EOL;
0707     switch (m_viInputModeManager->getCurrentViMode()) {
0708     case ViMode::NormalMode:
0709         m_commandRange.startLine = c.line();
0710         m_commandRange.startColumn = c.column();
0711         m_commandRange.endLine = c.line() + getCount() - 1;
0712         break;
0713     case ViMode::VisualMode:
0714     case ViMode::VisualLineMode:
0715         m = LineWise;
0716         break;
0717     case ViMode::VisualBlockMode:
0718         m_commandRange.normalize();
0719         m = Block;
0720         break;
0721     default:
0722         /* InsertMode and ReplaceMode will never call this method. */
0723         Q_ASSERT(false);
0724     }
0725 
0726     bool r = deleteRange(m_commandRange, m);
0727 
0728     switch (m) {
0729     case CharWise:
0730         c.setColumn(doc()->lineLength(c.line()) - 1);
0731         break;
0732     case LineWise:
0733         c.setLine(m_commandRange.startLine);
0734         c.setColumn(getFirstNonBlank(qMin(doc()->lastLine(), m_commandRange.startLine)));
0735         break;
0736     case Block:
0737         c.setLine(m_commandRange.startLine);
0738         c.setColumn(m_commandRange.startColumn - 1);
0739         break;
0740     }
0741 
0742     // make sure cursor position is valid after deletion
0743     if (c.line() < 0) {
0744         c.setLine(0);
0745     }
0746     if (c.line() > doc()->lastLine()) {
0747         c.setLine(doc()->lastLine());
0748     }
0749     if (c.column() > doc()->lineLength(c.line()) - 1) {
0750         c.setColumn(doc()->lineLength(c.line()) - 1);
0751     }
0752     if (c.column() < 0) {
0753         c.setColumn(0);
0754     }
0755 
0756     updateCursor(c);
0757 
0758     m_deleteCommand = true;
0759     return r;
0760 }
0761 
0762 bool NormalViMode::commandMakeLowercase()
0763 {
0764     KTextEditor::Cursor c = m_view->cursorPosition();
0765 
0766     OperationMode m = getOperationMode();
0767     QString text = getRange(m_commandRange, m);
0768     if (m == LineWise) {
0769         text.chop(1); // don't need '\n' at the end;
0770     }
0771     QString lowerCase = text.toLower();
0772 
0773     m_commandRange.normalize();
0774     KTextEditor::Cursor start(m_commandRange.startLine, m_commandRange.startColumn);
0775     KTextEditor::Cursor end(m_commandRange.endLine, m_commandRange.endColumn);
0776     KTextEditor::Range range(start, end);
0777 
0778     doc()->replaceText(range, lowerCase, m == Block);
0779 
0780     if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) {
0781         updateCursor(start);
0782     } else {
0783         updateCursor(c);
0784     }
0785 
0786     return true;
0787 }
0788 
0789 bool NormalViMode::commandMakeLowercaseLine()
0790 {
0791     KTextEditor::Cursor c(m_view->cursorPosition());
0792 
0793     if (doc()->lineLength(c.line()) == 0) {
0794         // Nothing to do.
0795         return true;
0796     }
0797 
0798     m_commandRange.startLine = c.line();
0799     m_commandRange.endLine = c.line() + getCount() - 1;
0800     m_commandRange.startColumn = 0;
0801     m_commandRange.endColumn = doc()->lineLength(c.line()) - 1;
0802 
0803     return commandMakeLowercase();
0804 }
0805 
0806 bool NormalViMode::commandMakeUppercase()
0807 {
0808     if (!m_commandRange.valid) {
0809         return false;
0810     }
0811     KTextEditor::Cursor c = m_view->cursorPosition();
0812     OperationMode m = getOperationMode();
0813     QString text = getRange(m_commandRange, m);
0814     if (m == LineWise) {
0815         text.chop(1); // don't need '\n' at the end;
0816     }
0817     QString upperCase = text.toUpper();
0818 
0819     m_commandRange.normalize();
0820     KTextEditor::Cursor start(m_commandRange.startLine, m_commandRange.startColumn);
0821     KTextEditor::Cursor end(m_commandRange.endLine, m_commandRange.endColumn);
0822     KTextEditor::Range range(start, end);
0823 
0824     doc()->replaceText(range, upperCase, m == Block);
0825     if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) {
0826         updateCursor(start);
0827     } else {
0828         updateCursor(c);
0829     }
0830 
0831     return true;
0832 }
0833 
0834 bool NormalViMode::commandMakeUppercaseLine()
0835 {
0836     KTextEditor::Cursor c(m_view->cursorPosition());
0837 
0838     if (doc()->lineLength(c.line()) == 0) {
0839         // Nothing to do.
0840         return true;
0841     }
0842 
0843     m_commandRange.startLine = c.line();
0844     m_commandRange.endLine = c.line() + getCount() - 1;
0845     m_commandRange.startColumn = 0;
0846     m_commandRange.endColumn = doc()->lineLength(c.line()) - 1;
0847 
0848     return commandMakeUppercase();
0849 }
0850 
0851 bool NormalViMode::commandChangeCase()
0852 {
0853     QString text;
0854     KTextEditor::Range range;
0855     KTextEditor::Cursor c(m_view->cursorPosition());
0856 
0857     // in visual mode, the range is from start position to end position...
0858     if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualMode || m_viInputModeManager->getCurrentViMode() == ViMode::VisualBlockMode) {
0859         KTextEditor::Cursor c2 = m_viInputModeManager->getViVisualMode()->getStart();
0860 
0861         if (c2 > c) {
0862             c2.setColumn(c2.column() + 1);
0863         } else {
0864             c.setColumn(c.column() + 1);
0865         }
0866 
0867         range.setRange(c, c2);
0868         // ... in visual line mode, the range is from column 0 on the first line to
0869         // the line length of the last line...
0870     } else if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualLineMode) {
0871         KTextEditor::Cursor c2 = m_viInputModeManager->getViVisualMode()->getStart();
0872 
0873         if (c2 > c) {
0874             c2.setColumn(doc()->lineLength(c2.line()));
0875             c.setColumn(0);
0876         } else {
0877             c.setColumn(doc()->lineLength(c.line()));
0878             c2.setColumn(0);
0879         }
0880 
0881         range.setRange(c, c2);
0882         // ... and in normal mode the range is from the current position to the
0883         // current position + count
0884     } else {
0885         KTextEditor::Cursor c2 = c;
0886         c2.setColumn(c.column() + getCount());
0887 
0888         if (c2.column() > doc()->lineLength(c.line())) {
0889             c2.setColumn(doc()->lineLength(c.line()));
0890         }
0891 
0892         range.setRange(c, c2);
0893     }
0894 
0895     bool block = m_viInputModeManager->getCurrentViMode() == ViMode::VisualBlockMode;
0896 
0897     // get the text the command should operate on
0898     text = doc()->text(range, block);
0899 
0900     // for every character, switch its case
0901     for (int i = 0; i < text.length(); i++) {
0902         if (text.at(i).isUpper()) {
0903             text[i] = text.at(i).toLower();
0904         } else if (text.at(i).isLower()) {
0905             text[i] = text.at(i).toUpper();
0906         }
0907     }
0908 
0909     // replace the old text with the modified text
0910     doc()->replaceText(range, text, block);
0911 
0912     // in normal mode, move the cursor to the right, in visual mode move the
0913     // cursor to the start of the selection
0914     if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) {
0915         updateCursor(range.end());
0916     } else {
0917         updateCursor(range.start());
0918     }
0919 
0920     return true;
0921 }
0922 
0923 bool NormalViMode::commandChangeCaseRange()
0924 {
0925     OperationMode m = getOperationMode();
0926     QString changedCase = getRange(m_commandRange, m);
0927     if (m == LineWise) {
0928         changedCase.chop(1); // don't need '\n' at the end;
0929     }
0930     KTextEditor::Range range = KTextEditor::Range(m_commandRange.startLine, m_commandRange.startColumn, m_commandRange.endLine, m_commandRange.endColumn);
0931     // get the text the command should operate on
0932     // for every character, switch its case
0933     for (int i = 0; i < changedCase.length(); i++) {
0934         if (changedCase.at(i).isUpper()) {
0935             changedCase[i] = changedCase.at(i).toLower();
0936         } else if (changedCase.at(i).isLower()) {
0937             changedCase[i] = changedCase.at(i).toUpper();
0938         }
0939     }
0940     doc()->replaceText(range, changedCase, m == Block);
0941     return true;
0942 }
0943 
0944 bool NormalViMode::commandChangeCaseLine()
0945 {
0946     KTextEditor::Cursor c(m_view->cursorPosition());
0947 
0948     if (doc()->lineLength(c.line()) == 0) {
0949         // Nothing to do.
0950         return true;
0951     }
0952 
0953     m_commandRange.startLine = c.line();
0954     m_commandRange.endLine = c.line() + getCount() - 1;
0955     m_commandRange.startColumn = 0;
0956     m_commandRange.endColumn = doc()->lineLength(c.line()) - 1; // -1 is for excluding '\0'
0957 
0958     if (!commandChangeCaseRange()) {
0959         return false;
0960     }
0961 
0962     KTextEditor::Cursor start(m_commandRange.startLine, m_commandRange.startColumn);
0963     if (getCount() > 1) {
0964         updateCursor(c);
0965     } else {
0966         updateCursor(start);
0967     }
0968     return true;
0969 }
0970 
0971 bool NormalViMode::commandOpenNewLineUnder()
0972 {
0973     doc()->setUndoMergeAllEdits(true);
0974 
0975     KTextEditor::Cursor c(m_view->cursorPosition());
0976 
0977     c.setColumn(doc()->lineLength(c.line()));
0978     updateCursor(c);
0979 
0980     doc()->newLine(m_view);
0981 
0982     m_stickyColumn = -1;
0983     startInsertMode();
0984     m_viInputModeManager->getViInsertMode()->setCount(getCount());
0985     m_viInputModeManager->getViInsertMode()->setCountedRepeatsBeginOnNewLine(true);
0986 
0987     return true;
0988 }
0989 
0990 bool NormalViMode::commandOpenNewLineOver()
0991 {
0992     doc()->setUndoMergeAllEdits(true);
0993 
0994     KTextEditor::Cursor c(m_view->cursorPosition());
0995 
0996     if (c.line() == 0) {
0997         doc()->insertLine(0, QString());
0998         c.setColumn(0);
0999         c.setLine(0);
1000         updateCursor(c);
1001     } else {
1002         c.setLine(c.line() - 1);
1003         c.setColumn(getLine(c.line()).length());
1004         updateCursor(c);
1005         doc()->newLine(m_view);
1006     }
1007 
1008     m_stickyColumn = -1;
1009     startInsertMode();
1010     m_viInputModeManager->getViInsertMode()->setCount(getCount());
1011     m_viInputModeManager->getViInsertMode()->setCountedRepeatsBeginOnNewLine(true);
1012 
1013     return true;
1014 }
1015 
1016 bool NormalViMode::commandJoinLines()
1017 {
1018     KTextEditor::Cursor c(m_view->cursorPosition());
1019 
1020     unsigned int from = c.line();
1021     unsigned int to = c.line() + ((getCount() == 1) ? 1 : getCount() - 1);
1022 
1023     // if we were given a range of lines, this information overrides the previous
1024     if (m_commandRange.startLine != -1 && m_commandRange.endLine != -1) {
1025         m_commandRange.normalize();
1026         c.setLine(m_commandRange.startLine);
1027         from = m_commandRange.startLine;
1028         to = m_commandRange.endLine;
1029     }
1030 
1031     if (to >= (unsigned int)doc()->lines()) {
1032         return false;
1033     }
1034 
1035     bool nonEmptyLineFound = false;
1036     for (unsigned int lineNum = from; lineNum <= to; lineNum++) {
1037         if (!doc()->line(lineNum).isEmpty()) {
1038             nonEmptyLineFound = true;
1039         }
1040     }
1041 
1042     const int firstNonWhitespaceOnLastLine = doc()->kateTextLine(to)->firstChar();
1043     QString leftTrimmedLastLine;
1044     if (firstNonWhitespaceOnLastLine != -1) {
1045         leftTrimmedLastLine = doc()->line(to).mid(firstNonWhitespaceOnLastLine);
1046     }
1047 
1048     joinLines(from, to);
1049 
1050     if (nonEmptyLineFound && leftTrimmedLastLine.isEmpty()) {
1051         // joinLines won't have added a trailing " ", whereas Vim does - follow suit.
1052         doc()->insertText(KTextEditor::Cursor(from, doc()->lineLength(from)), QStringLiteral(" "));
1053     }
1054 
1055     // Position cursor just before first non-whitesspace character of what was the last line joined.
1056     c.setColumn(doc()->lineLength(from) - leftTrimmedLastLine.length() - 1);
1057     if (c.column() >= 0) {
1058         updateCursor(c);
1059     }
1060 
1061     m_deleteCommand = true;
1062     return true;
1063 }
1064 
1065 bool NormalViMode::commandChange()
1066 {
1067     KTextEditor::Cursor c(m_view->cursorPosition());
1068 
1069     OperationMode m = getOperationMode();
1070 
1071     doc()->setUndoMergeAllEdits(true);
1072 
1073     commandDelete();
1074 
1075     if (m == LineWise) {
1076         // if we deleted several lines, insert an empty line and put the cursor there.
1077         doc()->insertLine(m_commandRange.startLine, QString());
1078         c.setLine(m_commandRange.startLine);
1079         c.setColumn(0);
1080     } else if (m == Block) {
1081         // block substitute can be simulated by first deleting the text
1082         // (done above) and then starting block prepend.
1083         return commandPrependToBlock();
1084     } else {
1085         if (m_commandRange.startLine < m_commandRange.endLine) {
1086             c.setLine(m_commandRange.startLine);
1087         }
1088         c.setColumn(m_commandRange.startColumn);
1089     }
1090 
1091     updateCursor(c);
1092     setCount(0); // The count was for the motion, not the insertion.
1093     commandEnterInsertMode();
1094 
1095     // correct indentation level
1096     if (m == LineWise) {
1097         m_view->align();
1098     }
1099 
1100     m_deleteCommand = true;
1101     return true;
1102 }
1103 
1104 bool NormalViMode::commandChangeToEOL()
1105 {
1106     commandDeleteToEOL();
1107 
1108     if (getOperationMode() == Block) {
1109         return commandPrependToBlock();
1110     }
1111 
1112     m_deleteCommand = true;
1113     return commandEnterInsertModeAppend();
1114 }
1115 
1116 bool NormalViMode::commandChangeLine()
1117 {
1118     m_deleteCommand = true;
1119     KTextEditor::Cursor c(m_view->cursorPosition());
1120     c.setColumn(0);
1121     updateCursor(c);
1122 
1123     doc()->setUndoMergeAllEdits(true);
1124 
1125     // if count >= 2 start by deleting the whole lines
1126     if (getCount() >= 2) {
1127         Range r(c.line(), 0, c.line() + getCount() - 2, 0, InclusiveMotion);
1128         deleteRange(r);
1129     }
1130 
1131     // ... then delete the _contents_ of the last line, but keep the line
1132     Range r(c.line(), c.column(), c.line(), doc()->lineLength(c.line()) - 1, InclusiveMotion);
1133     deleteRange(r, CharWise, true);
1134 
1135     // ... then enter insert mode
1136     if (getOperationMode() == Block) {
1137         return commandPrependToBlock();
1138     }
1139     commandEnterInsertModeAppend();
1140 
1141     // correct indentation level
1142     m_view->align();
1143 
1144     return true;
1145 }
1146 
1147 bool NormalViMode::commandSubstituteChar()
1148 {
1149     if (commandDeleteChar()) {
1150         // The count is only used for deletion of chars; the inserted text is not repeated
1151         setCount(0);
1152         return commandEnterInsertMode();
1153     }
1154 
1155     m_deleteCommand = true;
1156     return false;
1157 }
1158 
1159 bool NormalViMode::commandSubstituteLine()
1160 {
1161     m_deleteCommand = true;
1162     return commandChangeLine();
1163 }
1164 
1165 bool NormalViMode::commandYank()
1166 {
1167     bool r = false;
1168     QString yankedText;
1169 
1170     OperationMode m = getOperationMode();
1171     yankedText = getRange(m_commandRange, m);
1172 
1173     highlightYank(m_commandRange, m);
1174 
1175     QChar chosen_register = getChosenRegister(ZeroRegister);
1176     fillRegister(chosen_register, yankedText, m);
1177     yankToClipBoard(chosen_register, yankedText);
1178 
1179     return r;
1180 }
1181 
1182 bool NormalViMode::commandYankLine()
1183 {
1184     KTextEditor::Cursor c(m_view->cursorPosition());
1185     QString lines;
1186     int linenum = c.line();
1187 
1188     for (int i = 0; i < getCount(); i++) {
1189         lines.append(getLine(linenum + i) + QLatin1Char('\n'));
1190     }
1191 
1192     Range yankRange(linenum, 0, linenum + getCount() - 1, getLine(linenum + getCount() - 1).length(), InclusiveMotion);
1193     highlightYank(yankRange);
1194 
1195     QChar chosen_register = getChosenRegister(ZeroRegister);
1196     fillRegister(chosen_register, lines, LineWise);
1197     yankToClipBoard(chosen_register, lines);
1198 
1199     return true;
1200 }
1201 
1202 bool NormalViMode::commandYankToEOL()
1203 {
1204     OperationMode m = CharWise;
1205     KTextEditor::Cursor c(m_view->cursorPosition());
1206 
1207     MotionType motion = m_commandRange.motionType;
1208     m_commandRange.endLine = c.line() + getCount() - 1;
1209     m_commandRange.endColumn = doc()->lineLength(m_commandRange.endLine) - 1;
1210     m_commandRange.motionType = InclusiveMotion;
1211 
1212     switch (m_viInputModeManager->getCurrentViMode()) {
1213     case ViMode::NormalMode:
1214         m_commandRange.startLine = c.line();
1215         m_commandRange.startColumn = c.column();
1216         break;
1217     case ViMode::VisualMode:
1218     case ViMode::VisualLineMode:
1219         m = LineWise;
1220         {
1221             VisualViMode *visual = static_cast<VisualViMode *>(this);
1222             visual->setStart(KTextEditor::Cursor(visual->getStart().line(), 0));
1223         }
1224         break;
1225     case ViMode::VisualBlockMode:
1226         m = Block;
1227         break;
1228     default:
1229         /* InsertMode and ReplaceMode will never call this method. */
1230         Q_ASSERT(false);
1231     }
1232 
1233     const QString &yankedText = getRange(m_commandRange, m);
1234     m_commandRange.motionType = motion;
1235     highlightYank(m_commandRange);
1236 
1237     QChar chosen_register = getChosenRegister(ZeroRegister);
1238     fillRegister(chosen_register, yankedText, m);
1239     yankToClipBoard(chosen_register, yankedText);
1240 
1241     return true;
1242 }
1243 
1244 // Insert the text in the given register after the cursor position.
1245 // This is the non-g version of paste, so the cursor will usually
1246 // end up on the last character of the pasted text, unless the text
1247 // was multi-line or linewise in which case it will end up
1248 // on the *first* character of the pasted text(!)
1249 // If linewise, will paste after the current line.
1250 bool NormalViMode::commandPaste()
1251 {
1252     return paste(AfterCurrentPosition, false, false);
1253 }
1254 
1255 // As with commandPaste, except that the text is pasted *at* the cursor position
1256 bool NormalViMode::commandPasteBefore()
1257 {
1258     return paste(AtCurrentPosition, false, false);
1259 }
1260 
1261 // As with commandPaste, except that the cursor will generally be placed *after* the
1262 // last pasted character (assuming the last pasted character is not at  the end of the line).
1263 // If linewise, cursor will be at the beginning of the line *after* the last line of pasted text,
1264 // unless that line is the last line of the document; then it will be placed at the beginning of the
1265 // last line pasted.
1266 bool NormalViMode::commandgPaste()
1267 {
1268     return paste(AfterCurrentPosition, true, false);
1269 }
1270 
1271 // As with commandgPaste, except that it pastes *at* the current cursor position or, if linewise,
1272 // at the current line.
1273 bool NormalViMode::commandgPasteBefore()
1274 {
1275     return paste(AtCurrentPosition, true, false);
1276 }
1277 
1278 bool NormalViMode::commandIndentedPaste()
1279 {
1280     return paste(AfterCurrentPosition, false, true);
1281 }
1282 
1283 bool NormalViMode::commandIndentedPasteBefore()
1284 {
1285     return paste(AtCurrentPosition, false, true);
1286 }
1287 
1288 bool NormalViMode::commandDeleteChar()
1289 {
1290     KTextEditor::Cursor c(m_view->cursorPosition());
1291     Range r(c.line(), c.column(), c.line(), c.column() + getCount(), ExclusiveMotion);
1292 
1293     if (m_commandRange.startLine != -1 && m_commandRange.startColumn != -1) {
1294         r = m_commandRange;
1295     } else {
1296         if (r.endColumn > doc()->lineLength(r.startLine)) {
1297             r.endColumn = doc()->lineLength(r.startLine);
1298         }
1299     }
1300 
1301     // should delete entire lines if in visual line mode and selection in visual block mode
1302     OperationMode m = CharWise;
1303     if (m_viInputModeManager->getCurrentViMode() == VisualLineMode) {
1304         m = LineWise;
1305     } else if (m_viInputModeManager->getCurrentViMode() == VisualBlockMode) {
1306         m = Block;
1307     }
1308 
1309     m_deleteCommand = true;
1310     return deleteRange(r, m);
1311 }
1312 
1313 bool NormalViMode::commandDeleteCharBackward()
1314 {
1315     KTextEditor::Cursor c(m_view->cursorPosition());
1316 
1317     Range r(c.line(), c.column() - getCount(), c.line(), c.column(), ExclusiveMotion);
1318 
1319     if (m_commandRange.startLine != -1 && m_commandRange.startColumn != -1) {
1320         r = m_commandRange;
1321     } else {
1322         if (r.startColumn < 0) {
1323             r.startColumn = 0;
1324         }
1325     }
1326 
1327     // should delete entire lines if in visual line mode and selection in visual block mode
1328     OperationMode m = CharWise;
1329     if (m_viInputModeManager->getCurrentViMode() == VisualLineMode) {
1330         m = LineWise;
1331     } else if (m_viInputModeManager->getCurrentViMode() == VisualBlockMode) {
1332         m = Block;
1333     }
1334 
1335     m_deleteCommand = true;
1336     return deleteRange(r, m);
1337 }
1338 
1339 bool NormalViMode::commandReplaceCharacter()
1340 {
1341     QString key = KeyParser::self()->decodeKeySequence(m_keys.right(1));
1342 
1343     // Filter out some special keys.
1344     const int keyCode = KeyParser::self()->encoded2qt(m_keys.right(1));
1345     switch (keyCode) {
1346     case Qt::Key_Left:
1347     case Qt::Key_Right:
1348     case Qt::Key_Up:
1349     case Qt::Key_Down:
1350     case Qt::Key_Home:
1351     case Qt::Key_End:
1352     case Qt::Key_PageUp:
1353     case Qt::Key_PageDown:
1354     case Qt::Key_Delete:
1355     case Qt::Key_Insert:
1356     case Qt::Key_Backspace:
1357     case Qt::Key_CapsLock:
1358         return true;
1359     case Qt::Key_Return:
1360     case Qt::Key_Enter:
1361         key = QStringLiteral("\n");
1362     }
1363 
1364     bool r;
1365     if (m_viInputModeManager->isAnyVisualMode()) {
1366         OperationMode m = getOperationMode();
1367         QString text = getRange(m_commandRange, m);
1368 
1369         if (m == LineWise) {
1370             text.chop(1); // don't need '\n' at the end;
1371         }
1372 
1373         static const QRegularExpression nonNewlineRegex(QStringLiteral("[^\n]"));
1374         text.replace(nonNewlineRegex, key);
1375 
1376         m_commandRange.normalize();
1377         KTextEditor::Cursor start(m_commandRange.startLine, m_commandRange.startColumn);
1378         KTextEditor::Cursor end(m_commandRange.endLine, m_commandRange.endColumn);
1379         KTextEditor::Range range(start, end);
1380 
1381         r = doc()->replaceText(range, text, m == Block);
1382 
1383     } else {
1384         KTextEditor::Cursor c1(m_view->cursorPosition());
1385         KTextEditor::Cursor c2(m_view->cursorPosition());
1386 
1387         c2.setColumn(c2.column() + getCount());
1388 
1389         if (c2.column() > doc()->lineLength(m_view->cursorPosition().line())) {
1390             return false;
1391         }
1392 
1393         r = doc()->replaceText(KTextEditor::Range(c1, c2), key.repeated(getCount()));
1394         updateCursor(c1);
1395     }
1396     return r;
1397 }
1398 
1399 bool NormalViMode::commandSwitchToCmdLine()
1400 {
1401     QString initialText;
1402     if (m_viInputModeManager->isAnyVisualMode()) {
1403         // if in visual mode, make command range == visual selection
1404         m_viInputModeManager->getViVisualMode()->saveRangeMarks();
1405         initialText = QStringLiteral("'<,'>");
1406     } else if (getCount() != 1) {
1407         // if a count is given, the range [current line] to [current line] +
1408         // count should be prepended to the command line
1409         initialText = QLatin1String(".,.+") + QString::number(getCount() - 1);
1410     }
1411 
1412     m_viInputModeManager->inputAdapter()->showViModeEmulatedCommandBar();
1413     m_viInputModeManager->inputAdapter()->viModeEmulatedCommandBar()->init(EmulatedCommandBar::Command, initialText);
1414 
1415     m_commandShouldKeepSelection = true;
1416 
1417     return true;
1418 }
1419 
1420 bool NormalViMode::commandSearchBackward()
1421 {
1422     m_viInputModeManager->inputAdapter()->showViModeEmulatedCommandBar();
1423     m_viInputModeManager->inputAdapter()->viModeEmulatedCommandBar()->init(EmulatedCommandBar::SearchBackward);
1424     return true;
1425 }
1426 
1427 bool NormalViMode::commandSearchForward()
1428 {
1429     m_viInputModeManager->inputAdapter()->showViModeEmulatedCommandBar();
1430     m_viInputModeManager->inputAdapter()->viModeEmulatedCommandBar()->init(EmulatedCommandBar::SearchForward);
1431     return true;
1432 }
1433 
1434 bool NormalViMode::commandUndo()
1435 {
1436     // See BUG #328277
1437     m_viInputModeManager->clearCurrentChangeLog();
1438 
1439     if (doc()->undoCount() > 0) {
1440         const bool mapped = m_viInputModeManager->keyMapper()->isExecutingMapping();
1441 
1442         if (mapped) {
1443             doc()->editEnd();
1444         }
1445         doc()->undo();
1446         if (mapped) {
1447             doc()->editBegin();
1448         }
1449         if (m_viInputModeManager->isAnyVisualMode()) {
1450             m_viInputModeManager->getViVisualMode()->setStart(KTextEditor::Cursor(-1, -1));
1451             m_view->clearSelection();
1452             startNormalMode();
1453         }
1454         return true;
1455     }
1456     return false;
1457 }
1458 
1459 bool NormalViMode::commandRedo()
1460 {
1461     if (doc()->redoCount() > 0) {
1462         const bool mapped = m_viInputModeManager->keyMapper()->isExecutingMapping();
1463 
1464         if (mapped) {
1465             doc()->editEnd();
1466         }
1467         doc()->redo();
1468         if (mapped) {
1469             doc()->editBegin();
1470         }
1471         if (m_viInputModeManager->isAnyVisualMode()) {
1472             m_viInputModeManager->getViVisualMode()->setStart(KTextEditor::Cursor(-1, -1));
1473             m_view->clearSelection();
1474             startNormalMode();
1475         }
1476         return true;
1477     }
1478     return false;
1479 }
1480 
1481 bool NormalViMode::commandSetMark()
1482 {
1483     KTextEditor::Cursor c(m_view->cursorPosition());
1484 
1485     QChar mark = m_keys.at(m_keys.size() - 1);
1486     m_viInputModeManager->marks()->setUserMark(mark, c);
1487 
1488     return true;
1489 }
1490 
1491 bool NormalViMode::commandIndentLine()
1492 {
1493     KTextEditor::Cursor c(m_view->cursorPosition());
1494 
1495     doc()->indent(KTextEditor::Range(c.line(), 0, c.line() + getCount(), 0), 1);
1496 
1497     return true;
1498 }
1499 
1500 bool NormalViMode::commandUnindentLine()
1501 {
1502     KTextEditor::Cursor c(m_view->cursorPosition());
1503 
1504     doc()->indent(KTextEditor::Range(c.line(), 0, c.line() + getCount(), 0), -1);
1505 
1506     return true;
1507 }
1508 
1509 bool NormalViMode::commandIndentLines()
1510 {
1511     const bool downwards = m_commandRange.startLine < m_commandRange.endLine;
1512 
1513     m_commandRange.normalize();
1514 
1515     int line1 = m_commandRange.startLine;
1516     int line2 = m_commandRange.endLine;
1517     int col = getLine(line2).length();
1518     doc()->indent(KTextEditor::Range(line1, 0, line2, col), getCount());
1519 
1520     if (downwards) {
1521         updateCursor(KTextEditor::Cursor(m_commandRange.startLine, m_commandRange.startColumn));
1522     } else {
1523         updateCursor(KTextEditor::Cursor(m_commandRange.endLine, m_commandRange.endColumn));
1524     }
1525     return true;
1526 }
1527 
1528 bool NormalViMode::commandUnindentLines()
1529 {
1530     const bool downwards = m_commandRange.startLine < m_commandRange.endLine;
1531 
1532     m_commandRange.normalize();
1533 
1534     int line1 = m_commandRange.startLine;
1535     int line2 = m_commandRange.endLine;
1536 
1537     doc()->indent(KTextEditor::Range(line1, 0, line2, doc()->lineLength(line2)), -getCount());
1538 
1539     if (downwards) {
1540         updateCursor(KTextEditor::Cursor(m_commandRange.startLine, m_commandRange.startColumn));
1541     } else {
1542         updateCursor(KTextEditor::Cursor(m_commandRange.endLine, m_commandRange.endColumn));
1543     }
1544     return true;
1545 }
1546 
1547 bool NormalViMode::commandScrollPageDown()
1548 {
1549     if (getCount() < m_scroll_count_limit) {
1550         for (int i = 0; i < getCount(); i++) {
1551             m_view->pageDown();
1552         }
1553     }
1554     return true;
1555 }
1556 
1557 bool NormalViMode::commandScrollPageUp()
1558 {
1559     if (getCount() < m_scroll_count_limit) {
1560         for (int i = 0; i < getCount(); i++) {
1561             m_view->pageUp();
1562         }
1563     }
1564     return true;
1565 }
1566 
1567 bool NormalViMode::commandScrollHalfPageUp()
1568 {
1569     if (getCount() < m_scroll_count_limit) {
1570         for (int i = 0; i < getCount(); i++) {
1571             m_viewInternal->pageUp(false, true);
1572         }
1573     }
1574     return true;
1575 }
1576 
1577 bool NormalViMode::commandScrollHalfPageDown()
1578 {
1579     if (getCount() < m_scroll_count_limit) {
1580         for (int i = 0; i < getCount(); i++) {
1581             m_viewInternal->pageDown(false, true);
1582         }
1583     }
1584     return true;
1585 }
1586 
1587 bool NormalViMode::commandCenterView(bool onFirst)
1588 {
1589     KTextEditor::Cursor c(m_view->cursorPosition());
1590     const int virtualCenterLine = m_viewInternal->startLine() + linesDisplayed() / 2;
1591     const int virtualCursorLine = m_view->textFolding().lineToVisibleLine(c.line());
1592 
1593     scrollViewLines(virtualCursorLine - virtualCenterLine);
1594     if (onFirst) {
1595         c.setColumn(getFirstNonBlank());
1596         updateCursor(c);
1597     }
1598     return true;
1599 }
1600 
1601 bool NormalViMode::commandCenterViewOnNonBlank()
1602 {
1603     return commandCenterView(true);
1604 }
1605 
1606 bool NormalViMode::commandCenterViewOnCursor()
1607 {
1608     return commandCenterView(false);
1609 }
1610 
1611 bool NormalViMode::commandTopView(bool onFirst)
1612 {
1613     KTextEditor::Cursor c(m_view->cursorPosition());
1614     const int virtualCenterLine = m_viewInternal->startLine();
1615     const int virtualCursorLine = m_view->textFolding().lineToVisibleLine(c.line());
1616 
1617     scrollViewLines(virtualCursorLine - virtualCenterLine);
1618     if (onFirst) {
1619         c.setColumn(getFirstNonBlank());
1620         updateCursor(c);
1621     }
1622     return true;
1623 }
1624 
1625 bool NormalViMode::commandTopViewOnNonBlank()
1626 {
1627     return commandTopView(true);
1628 }
1629 
1630 bool NormalViMode::commandTopViewOnCursor()
1631 {
1632     return commandTopView(false);
1633 }
1634 
1635 bool NormalViMode::commandBottomView(bool onFirst)
1636 {
1637     KTextEditor::Cursor c(m_view->cursorPosition());
1638     const int virtualCenterLine = m_viewInternal->endLine();
1639     const int virtualCursorLine = m_view->textFolding().lineToVisibleLine(c.line());
1640 
1641     scrollViewLines(virtualCursorLine - virtualCenterLine);
1642     if (onFirst) {
1643         c.setColumn(getFirstNonBlank());
1644         updateCursor(c);
1645     }
1646     return true;
1647 }
1648 
1649 bool NormalViMode::commandBottomViewOnNonBlank()
1650 {
1651     return commandBottomView(true);
1652 }
1653 
1654 bool NormalViMode::commandBottomViewOnCursor()
1655 {
1656     return commandBottomView(false);
1657 }
1658 
1659 bool NormalViMode::commandAbort()
1660 {
1661     m_pendingResetIsDueToExit = true;
1662     reset();
1663     return true;
1664 }
1665 
1666 bool NormalViMode::commandPrintCharacterCode()
1667 {
1668     QChar ch = getCharUnderCursor();
1669 
1670     if (ch == QChar::Null) {
1671         message(QStringLiteral("NUL"));
1672     } else {
1673         int code = ch.unicode();
1674 
1675         QString dec = QString::number(code);
1676         QString hex = QString::number(code, 16);
1677         QString oct = QString::number(code, 8);
1678         if (oct.length() < 3) {
1679             oct.prepend(QLatin1Char('0'));
1680         }
1681         if (code > 0x80 && code < 0x1000) {
1682             hex.prepend((code < 0x100 ? QLatin1String("00") : QLatin1String("0")));
1683         }
1684         message(i18n("'%1' %2,  Hex %3,  Octal %4", ch, dec, hex, oct));
1685     }
1686 
1687     return true;
1688 }
1689 
1690 bool NormalViMode::commandRepeatLastChange()
1691 {
1692     const int repeatCount = getCount();
1693     resetParser();
1694     if (repeatCount > 1) {
1695         m_oneTimeCountOverride = repeatCount;
1696     }
1697     doc()->editStart();
1698     m_viInputModeManager->repeatLastChange();
1699     doc()->editEnd();
1700 
1701     return true;
1702 }
1703 
1704 bool NormalViMode::commandAlignLine()
1705 {
1706     const int line = m_view->cursorPosition().line();
1707     KTextEditor::Range alignRange(KTextEditor::Cursor(line, 0), KTextEditor::Cursor(line, 0));
1708 
1709     doc()->align(m_view, alignRange);
1710 
1711     return true;
1712 }
1713 
1714 bool NormalViMode::commandAlignLines()
1715 {
1716     m_commandRange.normalize();
1717 
1718     KTextEditor::Cursor start(m_commandRange.startLine, 0);
1719     KTextEditor::Cursor end(m_commandRange.endLine, 0);
1720 
1721     doc()->align(m_view, KTextEditor::Range(start, end));
1722 
1723     return true;
1724 }
1725 
1726 bool NormalViMode::commandAddToNumber()
1727 {
1728     addToNumberUnderCursor(getCount());
1729 
1730     return true;
1731 }
1732 
1733 bool NormalViMode::commandSubtractFromNumber()
1734 {
1735     addToNumberUnderCursor(-getCount());
1736 
1737     return true;
1738 }
1739 
1740 bool NormalViMode::commandPrependToBlock()
1741 {
1742     KTextEditor::Cursor c(m_view->cursorPosition());
1743 
1744     // move cursor to top left corner of selection
1745     m_commandRange.normalize();
1746     c.setColumn(m_commandRange.startColumn);
1747     c.setLine(m_commandRange.startLine);
1748     updateCursor(c);
1749 
1750     m_stickyColumn = -1;
1751     m_viInputModeManager->getViInsertMode()->setBlockPrependMode(m_commandRange);
1752     return startInsertMode();
1753 }
1754 
1755 bool NormalViMode::commandAppendToBlock()
1756 {
1757     KTextEditor::Cursor c(m_view->cursorPosition());
1758 
1759     m_commandRange.normalize();
1760     if (m_stickyColumn == (unsigned int)KateVi::EOL) { // append to EOL
1761         // move cursor to end of first line
1762         c.setLine(m_commandRange.startLine);
1763         c.setColumn(doc()->lineLength(c.line()));
1764         updateCursor(c);
1765         m_viInputModeManager->getViInsertMode()->setBlockAppendMode(m_commandRange, AppendEOL);
1766     } else {
1767         m_viInputModeManager->getViInsertMode()->setBlockAppendMode(m_commandRange, Append);
1768         // move cursor to top right corner of selection
1769         c.setColumn(m_commandRange.endColumn + 1);
1770         c.setLine(m_commandRange.startLine);
1771         updateCursor(c);
1772     }
1773 
1774     m_stickyColumn = -1;
1775 
1776     return startInsertMode();
1777 }
1778 
1779 bool NormalViMode::commandGoToNextJump()
1780 {
1781     KTextEditor::Cursor c = getNextJump(m_view->cursorPosition());
1782     updateCursor(c);
1783 
1784     return true;
1785 }
1786 
1787 bool NormalViMode::commandGoToPrevJump()
1788 {
1789     KTextEditor::Cursor c = getPrevJump(m_view->cursorPosition());
1790     updateCursor(c);
1791 
1792     return true;
1793 }
1794 
1795 bool NormalViMode::commandSwitchToLeftView()
1796 {
1797     switchView(Left);
1798     return true;
1799 }
1800 
1801 bool NormalViMode::commandSwitchToDownView()
1802 {
1803     switchView(Down);
1804     return true;
1805 }
1806 
1807 bool NormalViMode::commandSwitchToUpView()
1808 {
1809     switchView(Up);
1810     return true;
1811 }
1812 
1813 bool NormalViMode::commandSwitchToRightView()
1814 {
1815     switchView(Right);
1816     return true;
1817 }
1818 
1819 bool NormalViMode::commandSwitchToNextView()
1820 {
1821     switchView(Next);
1822     return true;
1823 }
1824 
1825 bool NormalViMode::commandSplitHoriz()
1826 {
1827     return executeKateCommand(QStringLiteral("split"));
1828 }
1829 
1830 bool NormalViMode::commandSplitVert()
1831 {
1832     return executeKateCommand(QStringLiteral("vsplit"));
1833 }
1834 
1835 bool NormalViMode::commandCloseView()
1836 {
1837     return executeKateCommand(QStringLiteral("close"));
1838 }
1839 
1840 bool NormalViMode::commandSwitchToNextTab()
1841 {
1842     QString command = QStringLiteral("bn");
1843 
1844     if (m_iscounted) {
1845         command = command + QLatin1Char(' ') + QString::number(getCount());
1846     }
1847 
1848     return executeKateCommand(command);
1849 }
1850 
1851 bool NormalViMode::commandSwitchToPrevTab()
1852 {
1853     QString command = QStringLiteral("bp");
1854 
1855     if (m_iscounted) {
1856         command = command + QLatin1Char(' ') + QString::number(getCount());
1857     }
1858 
1859     return executeKateCommand(command);
1860 }
1861 
1862 bool NormalViMode::commandFormatLine()
1863 {
1864     KTextEditor::Cursor c(m_view->cursorPosition());
1865 
1866     reformatLines(c.line(), c.line() + getCount() - 1);
1867 
1868     return true;
1869 }
1870 
1871 bool NormalViMode::commandFormatLines()
1872 {
1873     reformatLines(m_commandRange.startLine, m_commandRange.endLine);
1874     return true;
1875 }
1876 
1877 bool NormalViMode::commandCollapseToplevelNodes()
1878 {
1879 #if 0
1880     //FIXME FOLDING
1881     doc()->foldingTree()->collapseToplevelNodes();
1882 #endif
1883     return true;
1884 }
1885 
1886 bool NormalViMode::commandStartRecordingMacro()
1887 {
1888     const QChar reg = m_keys[m_keys.size() - 1];
1889     m_viInputModeManager->macroRecorder()->start(reg);
1890     return true;
1891 }
1892 
1893 bool NormalViMode::commandReplayMacro()
1894 {
1895     // "@<registername>" will have been added to the log; it needs to be cleared
1896     // *before* we replay the macro keypresses, else it can cause an infinite loop
1897     // if the macro contains a "."
1898     m_viInputModeManager->clearCurrentChangeLog();
1899     const QChar reg = m_keys[m_keys.size() - 1];
1900     const unsigned int count = getCount();
1901     resetParser();
1902     doc()->editBegin();
1903     for (unsigned int i = 0; i < count; i++) {
1904         m_viInputModeManager->macroRecorder()->replay(reg);
1905     }
1906     doc()->editEnd();
1907     return true;
1908 }
1909 
1910 bool NormalViMode::commandCloseNocheck()
1911 {
1912     return executeKateCommand(QStringLiteral("q!"));
1913 }
1914 
1915 bool NormalViMode::commandCloseWrite()
1916 {
1917     return executeKateCommand(QStringLiteral("wq"));
1918 }
1919 
1920 bool NormalViMode::commandCollapseLocal()
1921 {
1922 #if 0
1923     //FIXME FOLDING
1924     KTextEditor::Cursor c(m_view->cursorPosition());
1925     doc()->foldingTree()->collapseOne(c.line(), c.column());
1926 #endif
1927     return true;
1928 }
1929 
1930 bool NormalViMode::commandExpandAll()
1931 {
1932 #if 0
1933     //FIXME FOLDING
1934     doc()->foldingTree()->expandAll();
1935 #endif
1936     return true;
1937 }
1938 
1939 bool NormalViMode::commandExpandLocal()
1940 {
1941 #if 0
1942     //FIXME FOLDING
1943     KTextEditor::Cursor c(m_view->cursorPosition());
1944     doc()->foldingTree()->expandOne(c.line() + 1, c.column());
1945 #endif
1946     return true;
1947 }
1948 
1949 bool NormalViMode::commandToggleRegionVisibility()
1950 {
1951 #if 0
1952     //FIXME FOLDING
1953     KTextEditor::Cursor c(m_view->cursorPosition());
1954     doc()->foldingTree()->toggleRegionVisibility(c.line());
1955 #endif
1956     return true;
1957 }
1958 
1959 ////////////////////////////////////////////////////////////////////////////////
1960 // MOTIONS
1961 ////////////////////////////////////////////////////////////////////////////////
1962 
1963 Range NormalViMode::motionDown()
1964 {
1965     return goLineDown();
1966 }
1967 
1968 Range NormalViMode::motionUp()
1969 {
1970     return goLineUp();
1971 }
1972 
1973 Range NormalViMode::motionLeft()
1974 {
1975     KTextEditor::Cursor cursor(m_view->cursorPosition());
1976     m_stickyColumn = -1;
1977     Range r(cursor, ExclusiveMotion);
1978     r.endColumn -= getCount();
1979 
1980     if (r.endColumn < 0) {
1981         r.endColumn = 0;
1982     }
1983 
1984     return r;
1985 }
1986 
1987 Range NormalViMode::motionRight()
1988 {
1989     KTextEditor::Cursor cursor(m_view->cursorPosition());
1990     m_stickyColumn = -1;
1991     Range r(cursor, ExclusiveMotion);
1992     r.endColumn += getCount();
1993 
1994     // make sure end position isn't > line length
1995     if (r.endColumn > doc()->lineLength(r.endLine)) {
1996         r.endColumn = doc()->lineLength(r.endLine);
1997     }
1998 
1999     return r;
2000 }
2001 
2002 Range NormalViMode::motionPageDown()
2003 {
2004     KTextEditor::Cursor c(m_view->cursorPosition());
2005     Range r(c, InclusiveMotion);
2006     r.endLine += linesDisplayed();
2007 
2008     if (r.endLine >= doc()->lines()) {
2009         r.endLine = doc()->lines() - 1;
2010     }
2011     return r;
2012 }
2013 
2014 Range NormalViMode::motionPageUp()
2015 {
2016     KTextEditor::Cursor c(m_view->cursorPosition());
2017     Range r(c, InclusiveMotion);
2018     r.endLine -= linesDisplayed();
2019 
2020     if (r.endLine < 0) {
2021         r.endLine = 0;
2022     }
2023     return r;
2024 }
2025 
2026 Range NormalViMode::motionHalfPageDown()
2027 {
2028     if (commandScrollHalfPageDown()) {
2029         KTextEditor::Cursor c = m_view->cursorPosition();
2030         m_commandRange.endLine = c.line();
2031         m_commandRange.endColumn = c.column();
2032         return m_commandRange;
2033     }
2034     return Range::invalid();
2035 }
2036 
2037 Range NormalViMode::motionHalfPageUp()
2038 {
2039     if (commandScrollHalfPageUp()) {
2040         KTextEditor::Cursor c = m_view->cursorPosition();
2041         m_commandRange.endLine = c.line();
2042         m_commandRange.endColumn = c.column();
2043         return m_commandRange;
2044     }
2045     return Range::invalid();
2046 }
2047 
2048 Range NormalViMode::motionDownToFirstNonBlank()
2049 {
2050     Range r = goLineDown();
2051     r.endColumn = getFirstNonBlank(r.endLine);
2052     return r;
2053 }
2054 
2055 Range NormalViMode::motionUpToFirstNonBlank()
2056 {
2057     Range r = goLineUp();
2058     r.endColumn = getFirstNonBlank(r.endLine);
2059     return r;
2060 }
2061 
2062 Range NormalViMode::motionWordForward()
2063 {
2064     KTextEditor::Cursor c(m_view->cursorPosition());
2065     Range r(c, ExclusiveMotion);
2066 
2067     m_stickyColumn = -1;
2068 
2069     // Special case: If we're already on the very last character in the document, the motion should be
2070     // inclusive so the last character gets included
2071     if (c.line() == doc()->lines() - 1 && c.column() == doc()->lineLength(c.line()) - 1) {
2072         r.motionType = InclusiveMotion;
2073     } else {
2074         for (int i = 0; i < getCount(); i++) {
2075             c = findNextWordStart(c.line(), c.column());
2076 
2077             // stop when at the last char in the document
2078             if (!c.isValid()) {
2079                 c = doc()->documentEnd();
2080                 // if we still haven't "used up the count", make the motion inclusive, so that the last char
2081                 // is included
2082                 if (i < getCount()) {
2083                     r.motionType = InclusiveMotion;
2084                 }
2085                 break;
2086             }
2087         }
2088     }
2089 
2090     r.endColumn = c.column();
2091     r.endLine = c.line();
2092 
2093     return r;
2094 }
2095 
2096 Range NormalViMode::motionWordBackward()
2097 {
2098     KTextEditor::Cursor c(m_view->cursorPosition());
2099     Range r(c, ExclusiveMotion);
2100 
2101     m_stickyColumn = -1;
2102 
2103     for (int i = 0; i < getCount(); i++) {
2104         c = findPrevWordStart(c.line(), c.column());
2105 
2106         if (!c.isValid()) {
2107             c = KTextEditor::Cursor(0, 0);
2108             break;
2109         }
2110     }
2111 
2112     r.endColumn = c.column();
2113     r.endLine = c.line();
2114 
2115     return r;
2116 }
2117 
2118 Range NormalViMode::motionWORDForward()
2119 {
2120     KTextEditor::Cursor c(m_view->cursorPosition());
2121     Range r(c, ExclusiveMotion);
2122 
2123     m_stickyColumn = -1;
2124 
2125     for (int i = 0; i < getCount(); i++) {
2126         c = findNextWORDStart(c.line(), c.column());
2127 
2128         // stop when at the last char in the document
2129         if (c.line() == doc()->lines() - 1 && c.column() == doc()->lineLength(c.line()) - 1) {
2130             break;
2131         }
2132     }
2133 
2134     r.endColumn = c.column();
2135     r.endLine = c.line();
2136 
2137     return r;
2138 }
2139 
2140 Range NormalViMode::motionWORDBackward()
2141 {
2142     KTextEditor::Cursor c(m_view->cursorPosition());
2143     Range r(c, ExclusiveMotion);
2144 
2145     m_stickyColumn = -1;
2146 
2147     for (int i = 0; i < getCount(); i++) {
2148         c = findPrevWORDStart(c.line(), c.column());
2149 
2150         if (!c.isValid()) {
2151             c = KTextEditor::Cursor(0, 0);
2152         }
2153     }
2154 
2155     r.endColumn = c.column();
2156     r.endLine = c.line();
2157 
2158     return r;
2159 }
2160 
2161 Range NormalViMode::motionToEndOfWord()
2162 {
2163     KTextEditor::Cursor c(m_view->cursorPosition());
2164     Range r(c, InclusiveMotion);
2165 
2166     m_stickyColumn = -1;
2167 
2168     for (int i = 0; i < getCount(); i++) {
2169         c = findWordEnd(c.line(), c.column());
2170     }
2171 
2172     if (!c.isValid()) {
2173         c = doc()->documentEnd();
2174     }
2175 
2176     r.endColumn = c.column();
2177     r.endLine = c.line();
2178 
2179     return r;
2180 }
2181 
2182 Range NormalViMode::motionToEndOfWORD()
2183 {
2184     KTextEditor::Cursor c(m_view->cursorPosition());
2185     Range r(c, InclusiveMotion);
2186 
2187     m_stickyColumn = -1;
2188 
2189     for (int i = 0; i < getCount(); i++) {
2190         c = findWORDEnd(c.line(), c.column());
2191     }
2192 
2193     if (!c.isValid()) {
2194         c = doc()->documentEnd();
2195     }
2196 
2197     r.endColumn = c.column();
2198     r.endLine = c.line();
2199 
2200     return r;
2201 }
2202 
2203 Range NormalViMode::motionToEndOfPrevWord()
2204 {
2205     KTextEditor::Cursor c(m_view->cursorPosition());
2206     Range r(c, InclusiveMotion);
2207 
2208     m_stickyColumn = -1;
2209 
2210     for (int i = 0; i < getCount(); i++) {
2211         c = findPrevWordEnd(c.line(), c.column());
2212 
2213         if (c.isValid()) {
2214             r.endColumn = c.column();
2215             r.endLine = c.line();
2216         } else {
2217             r.endColumn = 0;
2218             r.endLine = 0;
2219             break;
2220         }
2221     }
2222 
2223     return r;
2224 }
2225 
2226 Range NormalViMode::motionToEndOfPrevWORD()
2227 {
2228     KTextEditor::Cursor c(m_view->cursorPosition());
2229     Range r(c, InclusiveMotion);
2230 
2231     m_stickyColumn = -1;
2232 
2233     for (int i = 0; i < getCount(); i++) {
2234         c = findPrevWORDEnd(c.line(), c.column());
2235 
2236         if (c.isValid()) {
2237             r.endColumn = c.column();
2238             r.endLine = c.line();
2239         } else {
2240             r.endColumn = 0;
2241             r.endLine = 0;
2242             break;
2243         }
2244     }
2245 
2246     return r;
2247 }
2248 
2249 Range NormalViMode::motionToEOL()
2250 {
2251     KTextEditor::Cursor c(m_view->cursorPosition());
2252 
2253     // set sticky column to a ridiculously high value so that the cursor will stick to EOL,
2254     // but only if it's a regular motion
2255     if (m_keys.size() == 1) {
2256         m_stickyColumn = KateVi::EOL;
2257     }
2258 
2259     unsigned int line = c.line() + (getCount() - 1);
2260     Range r(line, doc()->lineLength(line) - 1, InclusiveMotion);
2261 
2262     return r;
2263 }
2264 
2265 Range NormalViMode::motionToColumn0()
2266 {
2267     m_stickyColumn = -1;
2268     KTextEditor::Cursor cursor(m_view->cursorPosition());
2269     Range r(cursor.line(), 0, ExclusiveMotion);
2270 
2271     return r;
2272 }
2273 
2274 Range NormalViMode::motionToFirstCharacterOfLine()
2275 {
2276     m_stickyColumn = -1;
2277 
2278     KTextEditor::Cursor cursor(m_view->cursorPosition());
2279     int c = getFirstNonBlank();
2280 
2281     Range r(cursor.line(), c, ExclusiveMotion);
2282 
2283     return r;
2284 }
2285 
2286 Range NormalViMode::motionFindChar()
2287 {
2288     m_lastTFcommand = m_keys;
2289     KTextEditor::Cursor cursor(m_view->cursorPosition());
2290     QString line = getLine();
2291 
2292     m_stickyColumn = -1;
2293 
2294     int matchColumn = cursor.column();
2295 
2296     for (int i = 0; i < getCount(); i++) {
2297         matchColumn = line.indexOf(QStringView(m_keys).right(1), matchColumn + 1);
2298         if (matchColumn == -1) {
2299             break;
2300         }
2301     }
2302 
2303     Range r;
2304 
2305     if (matchColumn != -1) {
2306         r.endColumn = matchColumn;
2307         r.endLine = cursor.line();
2308     } else {
2309         return Range::invalid();
2310     }
2311 
2312     return r;
2313 }
2314 
2315 Range NormalViMode::motionFindCharBackward()
2316 {
2317     m_lastTFcommand = m_keys;
2318     KTextEditor::Cursor cursor(m_view->cursorPosition());
2319     QString line = getLine();
2320 
2321     m_stickyColumn = -1;
2322 
2323     int matchColumn = -1;
2324 
2325     int hits = 0;
2326     int i = cursor.column() - 1;
2327 
2328     while (hits != getCount() && i >= 0) {
2329         if (line.at(i) == m_keys.at(m_keys.size() - 1)) {
2330             hits++;
2331         }
2332 
2333         if (hits == getCount()) {
2334             matchColumn = i;
2335         }
2336 
2337         i--;
2338     }
2339 
2340     Range r(cursor, ExclusiveMotion);
2341 
2342     if (matchColumn != -1) {
2343         r.endColumn = matchColumn;
2344         r.endLine = cursor.line();
2345     } else {
2346         return Range::invalid();
2347     }
2348 
2349     return r;
2350 }
2351 
2352 Range NormalViMode::motionToChar()
2353 {
2354     m_lastTFcommand = m_keys;
2355     KTextEditor::Cursor cursor(m_view->cursorPosition());
2356     QString line = getLine();
2357 
2358     m_stickyColumn = -1;
2359     Range r;
2360     r.endColumn = -1;
2361     r.endLine = -1;
2362 
2363     int matchColumn = cursor.column() + (m_isRepeatedTFcommand ? 2 : 1);
2364 
2365     for (int i = 0; i < getCount(); i++) {
2366         const int lastColumn = matchColumn;
2367         matchColumn = line.indexOf(m_keys.right(1), matchColumn + ((i > 0) ? 1 : 0));
2368         if (matchColumn == -1) {
2369             if (m_isRepeatedTFcommand) {
2370                 matchColumn = lastColumn;
2371             } else {
2372                 return Range::invalid();
2373             }
2374             break;
2375         }
2376     }
2377 
2378     r.endColumn = matchColumn - 1;
2379     r.endLine = cursor.line();
2380 
2381     m_isRepeatedTFcommand = false;
2382     return r;
2383 }
2384 
2385 Range NormalViMode::motionToCharBackward()
2386 {
2387     m_lastTFcommand = m_keys;
2388     KTextEditor::Cursor cursor(m_view->cursorPosition());
2389     QString line = getLine();
2390 
2391     const int originalColumn = cursor.column();
2392     m_stickyColumn = -1;
2393 
2394     int matchColumn = originalColumn - 1;
2395 
2396     int hits = 0;
2397     int i = cursor.column() - (m_isRepeatedTFcommand ? 2 : 1);
2398 
2399     Range r(cursor, ExclusiveMotion);
2400 
2401     while (hits != getCount() && i >= 0) {
2402         if (line.at(i) == m_keys.at(m_keys.size() - 1)) {
2403             hits++;
2404         }
2405 
2406         if (hits == getCount()) {
2407             matchColumn = i;
2408         }
2409 
2410         i--;
2411     }
2412 
2413     if (hits == getCount()) {
2414         r.endColumn = matchColumn + 1;
2415         r.endLine = cursor.line();
2416     } else {
2417         r.valid = false;
2418     }
2419 
2420     m_isRepeatedTFcommand = false;
2421 
2422     return r;
2423 }
2424 
2425 Range NormalViMode::motionRepeatlastTF()
2426 {
2427     if (!m_lastTFcommand.isEmpty()) {
2428         m_isRepeatedTFcommand = true;
2429         m_keys = m_lastTFcommand;
2430         if (m_keys.at(0) == QLatin1Char('f')) {
2431             return motionFindChar();
2432         } else if (m_keys.at(0) == QLatin1Char('F')) {
2433             return motionFindCharBackward();
2434         } else if (m_keys.at(0) == QLatin1Char('t')) {
2435             return motionToChar();
2436         } else if (m_keys.at(0) == QLatin1Char('T')) {
2437             return motionToCharBackward();
2438         }
2439     }
2440 
2441     // there was no previous t/f command
2442     return Range::invalid();
2443 }
2444 
2445 Range NormalViMode::motionRepeatlastTFBackward()
2446 {
2447     if (!m_lastTFcommand.isEmpty()) {
2448         m_isRepeatedTFcommand = true;
2449         m_keys = m_lastTFcommand;
2450         if (m_keys.at(0) == QLatin1Char('f')) {
2451             return motionFindCharBackward();
2452         } else if (m_keys.at(0) == QLatin1Char('F')) {
2453             return motionFindChar();
2454         } else if (m_keys.at(0) == QLatin1Char('t')) {
2455             return motionToCharBackward();
2456         } else if (m_keys.at(0) == QLatin1Char('T')) {
2457             return motionToChar();
2458         }
2459     }
2460 
2461     // there was no previous t/f command
2462     return Range::invalid();
2463 }
2464 
2465 Range NormalViMode::motionToLineFirst()
2466 {
2467     Range r(getCount() - 1, 0, InclusiveMotion);
2468     m_stickyColumn = -1;
2469 
2470     if (r.endLine > doc()->lines() - 1) {
2471         r.endLine = doc()->lines() - 1;
2472     }
2473     r.jump = true;
2474 
2475     return r;
2476 }
2477 
2478 Range NormalViMode::motionToLineLast()
2479 {
2480     Range r(doc()->lines() - 1, 0, InclusiveMotion);
2481     m_stickyColumn = -1;
2482 
2483     // don't use getCount() here, no count and a count of 1 is different here...
2484     if (m_count != 0) {
2485         r.endLine = m_count - 1;
2486     }
2487 
2488     if (r.endLine > doc()->lines() - 1) {
2489         r.endLine = doc()->lines() - 1;
2490     }
2491     r.jump = true;
2492 
2493     return r;
2494 }
2495 
2496 Range NormalViMode::motionToScreenColumn()
2497 {
2498     m_stickyColumn = -1;
2499 
2500     KTextEditor::Cursor c(m_view->cursorPosition());
2501 
2502     int column = getCount() - 1;
2503 
2504     if (doc()->lineLength(c.line()) - 1 < (int)getCount() - 1) {
2505         column = doc()->lineLength(c.line()) - 1;
2506     }
2507 
2508     return Range(c.line(), column, ExclusiveMotion);
2509 }
2510 
2511 Range NormalViMode::motionToMark()
2512 {
2513     Range r;
2514 
2515     m_stickyColumn = -1;
2516 
2517     QChar reg = m_keys.at(m_keys.size() - 1);
2518 
2519     KTextEditor::Cursor c = m_viInputModeManager->marks()->getMarkPosition(reg);
2520     if (c.isValid()) {
2521         r.endLine = c.line();
2522         r.endColumn = c.column();
2523     } else {
2524         error(i18n("Mark not set: %1", m_keys.right(1)));
2525         r.valid = false;
2526     }
2527 
2528     r.jump = true;
2529 
2530     return r;
2531 }
2532 
2533 Range NormalViMode::motionToMarkLine()
2534 {
2535     Range r = motionToMark();
2536     r.endColumn = getFirstNonBlank(r.endLine);
2537     r.jump = true;
2538     m_stickyColumn = -1;
2539     return r;
2540 }
2541 
2542 Range NormalViMode::motionToMatchingItem()
2543 {
2544     Range r;
2545     int lines = doc()->lines();
2546 
2547     // If counted, then it's not a motion to matching item anymore,
2548     // but a motion to the N'th percentage of the document
2549     if (isCounted()) {
2550         int count = getCount();
2551         if (count > 100) {
2552             return r;
2553         }
2554         r.endLine = qRound(lines * count / 100.0) - 1;
2555         r.endColumn = 0;
2556         return r;
2557     }
2558 
2559     KTextEditor::Cursor c(m_view->cursorPosition());
2560 
2561     QString l = getLine();
2562     int n1 = l.indexOf(m_matchItemRegex, c.column());
2563 
2564     m_stickyColumn = -1;
2565 
2566     if (n1 < 0) {
2567         return Range::invalid();
2568     }
2569 
2570     const auto bracketChar = l.at(n1);
2571     // use Kate's built-in matching bracket finder for brackets
2572     if (bracketChar == QLatin1Char('(') || bracketChar == QLatin1Char(')') || bracketChar == QLatin1Char('{') || bracketChar == QLatin1Char('}')
2573         || bracketChar == QLatin1Char('[') || bracketChar == QLatin1Char(']')) {
2574         // findMatchingBracket requires us to move the cursor to the
2575         // first bracket, but we don't want the cursor to really move
2576         // in case this is e.g. a yank, so restore it to its original
2577         // position afterwards.
2578         c.setColumn(n1);
2579         const KTextEditor::Cursor oldCursorPos = m_view->cursorPosition();
2580         updateCursor(c);
2581 
2582         // find the matching one
2583         c = m_viewInternal->findMatchingBracket();
2584         if (c > m_view->cursorPosition()) {
2585             c.setColumn(c.column() - 1);
2586         }
2587         m_view->setCursorPosition(oldCursorPos);
2588     } else {
2589         // text item we want to find a matching item for
2590         static const QRegularExpression boundaryRegex(QStringLiteral("\\b|\\s|$"));
2591         const int n2 = l.indexOf(boundaryRegex, n1);
2592         QString item = l.mid(n1, n2 - n1);
2593         QString matchingItem = m_matchingItems[item];
2594 
2595         int toFind = 1;
2596         int line = c.line();
2597         int column = n2 - item.length();
2598         bool reverse = false;
2599 
2600         if (matchingItem.startsWith(QLatin1Char('-'))) {
2601             matchingItem.remove(0, 1); // remove the '-'
2602             reverse = true;
2603         }
2604 
2605         // make sure we don't hit the text item we started the search from
2606         if (column == 0 && reverse) {
2607             column -= item.length();
2608         }
2609 
2610         int itemIdx;
2611         int matchItemIdx;
2612 
2613         while (toFind > 0) {
2614             if (reverse) {
2615                 itemIdx = l.lastIndexOf(item, column - 1);
2616                 matchItemIdx = l.lastIndexOf(matchingItem, column - 1);
2617 
2618                 if (itemIdx != -1 && (matchItemIdx == -1 || itemIdx > matchItemIdx)) {
2619                     ++toFind;
2620                 }
2621             } else {
2622                 itemIdx = l.indexOf(item, column);
2623                 matchItemIdx = l.indexOf(matchingItem, column);
2624 
2625                 if (itemIdx != -1 && (matchItemIdx == -1 || itemIdx < matchItemIdx)) {
2626                     ++toFind;
2627                 }
2628             }
2629 
2630             if (matchItemIdx != -1 || itemIdx != -1) {
2631                 if (!reverse) {
2632                     column = qMin((unsigned int)itemIdx, (unsigned int)matchItemIdx);
2633                 } else {
2634                     column = qMax(itemIdx, matchItemIdx);
2635                 }
2636             }
2637 
2638             if (matchItemIdx != -1) { // match on current line
2639                 if (matchItemIdx == column) {
2640                     --toFind;
2641                     c.setLine(line);
2642                     c.setColumn(column);
2643                 }
2644             } else { // no match, advance one line if possible
2645                 (reverse) ? --line : ++line;
2646                 column = 0;
2647 
2648                 if ((!reverse && line >= lines) || (reverse && line < 0)) {
2649                     r.valid = false;
2650                     break;
2651                 } else {
2652                     l = getLine(line);
2653                 }
2654             }
2655         }
2656     }
2657 
2658     r.endLine = c.line();
2659     r.endColumn = c.column();
2660     r.jump = true;
2661 
2662     return r;
2663 }
2664 
2665 Range NormalViMode::motionToNextBraceBlockStart()
2666 {
2667     Range r;
2668 
2669     m_stickyColumn = -1;
2670 
2671     int line = findLineStartingWitchChar(QLatin1Char('{'), getCount());
2672 
2673     if (line == -1) {
2674         return Range::invalid();
2675     }
2676 
2677     r.endLine = line;
2678     r.endColumn = 0;
2679     r.jump = true;
2680 
2681     if (motionWillBeUsedWithCommand()) {
2682         // Delete from cursor (inclusive) to the '{' (exclusive).
2683         // If we are on the first column, then delete the entire current line.
2684         r.motionType = ExclusiveMotion;
2685         if (m_view->cursorPosition().column() != 0) {
2686             r.endLine--;
2687             r.endColumn = doc()->lineLength(r.endLine);
2688         }
2689     }
2690 
2691     return r;
2692 }
2693 
2694 Range NormalViMode::motionToPreviousBraceBlockStart()
2695 {
2696     Range r;
2697 
2698     m_stickyColumn = -1;
2699 
2700     int line = findLineStartingWitchChar(QLatin1Char('{'), getCount(), false);
2701 
2702     if (line == -1) {
2703         return Range::invalid();
2704     }
2705 
2706     r.endLine = line;
2707     r.endColumn = 0;
2708     r.jump = true;
2709 
2710     if (motionWillBeUsedWithCommand()) {
2711         // With a command, do not include the { or the cursor position.
2712         r.motionType = ExclusiveMotion;
2713     }
2714 
2715     return r;
2716 }
2717 
2718 Range NormalViMode::motionToNextBraceBlockEnd()
2719 {
2720     Range r;
2721 
2722     m_stickyColumn = -1;
2723 
2724     int line = findLineStartingWitchChar(QLatin1Char('}'), getCount());
2725 
2726     if (line == -1) {
2727         return Range::invalid();
2728     }
2729 
2730     r.endLine = line;
2731     r.endColumn = 0;
2732     r.jump = true;
2733 
2734     if (motionWillBeUsedWithCommand()) {
2735         // Delete from cursor (inclusive) to the '}' (exclusive).
2736         // If we are on the first column, then delete the entire current line.
2737         r.motionType = ExclusiveMotion;
2738         if (m_view->cursorPosition().column() != 0) {
2739             r.endLine--;
2740             r.endColumn = doc()->lineLength(r.endLine);
2741         }
2742     }
2743 
2744     return r;
2745 }
2746 
2747 Range NormalViMode::motionToPreviousBraceBlockEnd()
2748 {
2749     Range r;
2750 
2751     m_stickyColumn = -1;
2752 
2753     int line = findLineStartingWitchChar(QLatin1Char('}'), getCount(), false);
2754 
2755     if (line == -1) {
2756         return Range::invalid();
2757     }
2758 
2759     r.endLine = line;
2760     r.endColumn = 0;
2761     r.jump = true;
2762 
2763     if (motionWillBeUsedWithCommand()) {
2764         r.motionType = ExclusiveMotion;
2765     }
2766 
2767     return r;
2768 }
2769 
2770 Range NormalViMode::motionToNextOccurrence()
2771 {
2772     const QString word = getWordUnderCursor();
2773     Searcher *searcher = m_viInputModeManager->searcher();
2774     const Range match = searcher->findWordForMotion(word, false, getWordRangeUnderCursor().start(), getCount());
2775     if (searcher->lastSearchWrapped()) {
2776         m_view->showSearchWrappedHint(/*isReverseSearch*/ false);
2777     }
2778 
2779     return Range(match.startLine, match.startColumn, ExclusiveMotion);
2780 }
2781 
2782 Range NormalViMode::motionToPrevOccurrence()
2783 {
2784     const QString word = getWordUnderCursor();
2785     Searcher *searcher = m_viInputModeManager->searcher();
2786     const Range match = searcher->findWordForMotion(word, true, getWordRangeUnderCursor().start(), getCount());
2787     if (searcher->lastSearchWrapped()) {
2788         m_view->showSearchWrappedHint(/*isReverseSearch*/ true);
2789     }
2790 
2791     return Range(match.startLine, match.startColumn, ExclusiveMotion);
2792 }
2793 
2794 Range NormalViMode::motionToFirstLineOfWindow()
2795 {
2796     int lines_to_go;
2797     if (linesDisplayed() <= (unsigned int)m_viewInternal->endLine()) {
2798         lines_to_go = m_viewInternal->endLine() - linesDisplayed() - m_view->cursorPosition().line() + 1;
2799     } else {
2800         lines_to_go = -m_view->cursorPosition().line();
2801     }
2802 
2803     Range r = goLineUpDown(lines_to_go);
2804     r.endColumn = getFirstNonBlank(r.endLine);
2805     return r;
2806 }
2807 
2808 Range NormalViMode::motionToMiddleLineOfWindow()
2809 {
2810     int lines_to_go;
2811     if (linesDisplayed() <= (unsigned int)m_viewInternal->endLine()) {
2812         lines_to_go = m_viewInternal->endLine() - linesDisplayed() / 2 - m_view->cursorPosition().line();
2813     } else {
2814         lines_to_go = m_viewInternal->endLine() / 2 - m_view->cursorPosition().line();
2815     }
2816 
2817     Range r = goLineUpDown(lines_to_go);
2818     r.endColumn = getFirstNonBlank(r.endLine);
2819     return r;
2820 }
2821 
2822 Range NormalViMode::motionToLastLineOfWindow()
2823 {
2824     int lines_to_go;
2825     if (linesDisplayed() <= (unsigned int)m_viewInternal->endLine()) {
2826         lines_to_go = m_viewInternal->endLine() - m_view->cursorPosition().line();
2827     } else {
2828         lines_to_go = m_viewInternal->endLine() - m_view->cursorPosition().line();
2829     }
2830 
2831     Range r = goLineUpDown(lines_to_go);
2832     r.endColumn = getFirstNonBlank(r.endLine);
2833     return r;
2834 }
2835 
2836 Range NormalViMode::motionToNextVisualLine()
2837 {
2838     return goVisualLineUpDown(getCount());
2839 }
2840 
2841 Range NormalViMode::motionToPrevVisualLine()
2842 {
2843     return goVisualLineUpDown(-getCount());
2844 }
2845 
2846 Range NormalViMode::motionToPreviousSentence()
2847 {
2848     KTextEditor::Cursor c = findSentenceStart();
2849     int linenum = c.line();
2850     int column = c.column();
2851     const bool skipSpaces = doc()->line(linenum).isEmpty();
2852 
2853     if (skipSpaces) {
2854         linenum--;
2855         if (linenum >= 0) {
2856             column = doc()->line(linenum).size() - 1;
2857         }
2858     }
2859 
2860     for (int i = linenum; i >= 0; i--) {
2861         const QString &line = doc()->line(i);
2862 
2863         if (line.isEmpty() && !skipSpaces) {
2864             return Range(i, 0, InclusiveMotion);
2865         }
2866 
2867         if (column < 0 && !line.isEmpty()) {
2868             column = line.size() - 1;
2869         }
2870 
2871         for (int j = column; j >= 0; j--) {
2872             if (skipSpaces || QStringLiteral(".?!").indexOf(line.at(j)) != -1) {
2873                 c.setLine(i);
2874                 c.setColumn(j);
2875                 updateCursor(c);
2876                 c = findSentenceStart();
2877                 return Range(c, InclusiveMotion);
2878             }
2879         }
2880         column = line.size() - 1;
2881     }
2882     return Range(0, 0, InclusiveMotion);
2883 }
2884 
2885 Range NormalViMode::motionToNextSentence()
2886 {
2887     KTextEditor::Cursor c = findSentenceEnd();
2888     int linenum = c.line();
2889     int column = c.column() + 1;
2890     const bool skipSpaces = doc()->line(linenum).isEmpty();
2891 
2892     for (int i = linenum; i < doc()->lines(); i++) {
2893         const QString &line = doc()->line(i);
2894 
2895         if (line.isEmpty() && !skipSpaces) {
2896             return Range(i, 0, InclusiveMotion);
2897         }
2898 
2899         for (int j = column; j < line.size(); j++) {
2900             if (!line.at(j).isSpace()) {
2901                 return Range(i, j, InclusiveMotion);
2902             }
2903         }
2904         column = 0;
2905     }
2906 
2907     c = doc()->documentEnd();
2908     return Range(c, InclusiveMotion);
2909 }
2910 
2911 Range NormalViMode::motionToBeforeParagraph()
2912 {
2913     KTextEditor::Cursor c(m_view->cursorPosition());
2914 
2915     int line = c.line();
2916 
2917     m_stickyColumn = -1;
2918 
2919     for (int i = 0; i < getCount(); i++) {
2920         // advance at least one line, but if there are consecutive blank lines
2921         // skip them all
2922         do {
2923             line--;
2924         } while (line >= 0 && getLine(line + 1).length() == 0);
2925         while (line > 0 && getLine(line).length() != 0) {
2926             line--;
2927         }
2928     }
2929 
2930     if (line < 0) {
2931         line = 0;
2932     }
2933 
2934     Range r(line, 0, InclusiveMotion);
2935 
2936     return r;
2937 }
2938 
2939 Range NormalViMode::motionToAfterParagraph()
2940 {
2941     KTextEditor::Cursor c(m_view->cursorPosition());
2942 
2943     int line = c.line();
2944 
2945     m_stickyColumn = -1;
2946 
2947     for (int i = 0; i < getCount(); i++) {
2948         // advance at least one line, but if there are consecutive blank lines
2949         // skip them all
2950         do {
2951             line++;
2952         } while (line <= doc()->lines() - 1 && getLine(line - 1).length() == 0);
2953         while (line < doc()->lines() - 1 && getLine(line).length() != 0) {
2954             line++;
2955         }
2956     }
2957 
2958     if (line >= doc()->lines()) {
2959         line = doc()->lines() - 1;
2960     }
2961 
2962     // if we ended up on the last line, the cursor should be placed on the last column
2963     int column = (line == doc()->lines() - 1) ? qMax(getLine(line).length() - 1, 0) : 0;
2964 
2965     return Range(line, column, InclusiveMotion);
2966 }
2967 
2968 Range NormalViMode::motionToIncrementalSearchMatch()
2969 {
2970     return Range(m_positionWhenIncrementalSearchBegan.line(),
2971                  m_positionWhenIncrementalSearchBegan.column(),
2972                  m_view->cursorPosition().line(),
2973                  m_view->cursorPosition().column(),
2974                  ExclusiveMotion);
2975 }
2976 
2977 ////////////////////////////////////////////////////////////////////////////////
2978 // TEXT OBJECTS
2979 ////////////////////////////////////////////////////////////////////////////////
2980 
2981 Range NormalViMode::textObjectAWord()
2982 {
2983     KTextEditor::Cursor c(m_view->cursorPosition());
2984 
2985     KTextEditor::Cursor c1 = c;
2986 
2987     bool startedOnSpace = false;
2988     if (doc()->characterAt(c).isSpace()) {
2989         startedOnSpace = true;
2990     } else {
2991         c1 = findPrevWordStart(c.line(), c.column() + 1, true);
2992         if (!c1.isValid()) {
2993             c1 = KTextEditor::Cursor(0, 0);
2994         }
2995     }
2996     KTextEditor::Cursor c2 = KTextEditor::Cursor(c.line(), c.column() - 1);
2997     for (int i = 1; i <= getCount(); i++) {
2998         c2 = findWordEnd(c2.line(), c2.column());
2999     }
3000     if (!c1.isValid() || !c2.isValid()) {
3001         return Range::invalid();
3002     }
3003     // Adhere to some of Vim's bizarre rules of whether to swallow ensuing spaces or not.
3004     // Don't ask ;)
3005     const KTextEditor::Cursor nextWordStart = findNextWordStart(c2.line(), c2.column());
3006     if (nextWordStart.isValid() && nextWordStart.line() == c2.line()) {
3007         if (!startedOnSpace) {
3008             c2 = KTextEditor::Cursor(nextWordStart.line(), nextWordStart.column() - 1);
3009         }
3010     } else {
3011         c2 = KTextEditor::Cursor(c2.line(), doc()->lineLength(c2.line()) - 1);
3012     }
3013     bool swallowCarriageReturnAtEndOfLine = false;
3014     if (c2.line() != c.line() && c2.column() == doc()->lineLength(c2.line()) - 1) {
3015         // Greedily descend to the next line, so as to swallow the carriage return on this line.
3016         c2 = KTextEditor::Cursor(c2.line() + 1, 0);
3017         swallowCarriageReturnAtEndOfLine = true;
3018     }
3019     const bool swallowPrecedingSpaces =
3020         (c2.column() == doc()->lineLength(c2.line()) - 1 && !doc()->characterAt(c2).isSpace()) || startedOnSpace || swallowCarriageReturnAtEndOfLine;
3021     if (swallowPrecedingSpaces) {
3022         if (c1.column() != 0) {
3023             const KTextEditor::Cursor previousNonSpace = findPrevWordEnd(c.line(), c.column());
3024             if (previousNonSpace.isValid() && previousNonSpace.line() == c1.line()) {
3025                 c1 = KTextEditor::Cursor(previousNonSpace.line(), previousNonSpace.column() + 1);
3026             } else if (startedOnSpace || swallowCarriageReturnAtEndOfLine) {
3027                 c1 = KTextEditor::Cursor(c1.line(), 0);
3028             }
3029         }
3030     }
3031 
3032     return Range(c1, c2, !swallowCarriageReturnAtEndOfLine ? InclusiveMotion : ExclusiveMotion);
3033 }
3034 
3035 Range NormalViMode::textObjectInnerWord()
3036 {
3037     KTextEditor::Cursor c(m_view->cursorPosition());
3038 
3039     KTextEditor::Cursor c1 = findPrevWordStart(c.line(), c.column() + 1, true);
3040     if (!c1.isValid()) {
3041         c1 = KTextEditor::Cursor(0, 0);
3042     }
3043     // need to start search in column-1 because it might be a one-character word
3044     KTextEditor::Cursor c2(c.line(), c.column() - 1);
3045 
3046     for (int i = 0; i < getCount(); i++) {
3047         c2 = findWordEnd(c2.line(), c2.column(), true);
3048     }
3049 
3050     if (!c2.isValid()) {
3051         c2 = doc()->documentEnd();
3052     }
3053 
3054     // sanity check
3055     if (c1.line() != c2.line() || c1.column() > c2.column()) {
3056         return Range::invalid();
3057     }
3058     return Range(c1, c2, InclusiveMotion);
3059 }
3060 
3061 Range NormalViMode::textObjectAWORD()
3062 {
3063     KTextEditor::Cursor c(m_view->cursorPosition());
3064 
3065     KTextEditor::Cursor c1 = c;
3066 
3067     bool startedOnSpace = false;
3068     if (doc()->characterAt(c).isSpace()) {
3069         startedOnSpace = true;
3070     } else {
3071         c1 = findPrevWORDStart(c.line(), c.column() + 1, true);
3072         if (!c1.isValid()) {
3073             c1 = KTextEditor::Cursor(0, 0);
3074         }
3075     }
3076     KTextEditor::Cursor c2 = KTextEditor::Cursor(c.line(), c.column() - 1);
3077     for (int i = 1; i <= getCount(); i++) {
3078         c2 = findWORDEnd(c2.line(), c2.column());
3079     }
3080     if (!c1.isValid() || !c2.isValid()) {
3081         return Range::invalid();
3082     }
3083     // Adhere to some of Vim's bizarre rules of whether to swallow ensuing spaces or not.
3084     // Don't ask ;)
3085     const KTextEditor::Cursor nextWordStart = findNextWordStart(c2.line(), c2.column());
3086     if (nextWordStart.isValid() && nextWordStart.line() == c2.line()) {
3087         if (!startedOnSpace) {
3088             c2 = KTextEditor::Cursor(nextWordStart.line(), nextWordStart.column() - 1);
3089         }
3090     } else {
3091         c2 = KTextEditor::Cursor(c2.line(), doc()->lineLength(c2.line()) - 1);
3092     }
3093     bool swallowCarriageReturnAtEndOfLine = false;
3094     if (c2.line() != c.line() && c2.column() == doc()->lineLength(c2.line()) - 1) {
3095         // Greedily descend to the next line, so as to swallow the carriage return on this line.
3096         c2 = KTextEditor::Cursor(c2.line() + 1, 0);
3097         swallowCarriageReturnAtEndOfLine = true;
3098     }
3099     const bool swallowPrecedingSpaces =
3100         (c2.column() == doc()->lineLength(c2.line()) - 1 && !doc()->characterAt(c2).isSpace()) || startedOnSpace || swallowCarriageReturnAtEndOfLine;
3101     if (swallowPrecedingSpaces) {
3102         if (c1.column() != 0) {
3103             const KTextEditor::Cursor previousNonSpace = findPrevWORDEnd(c.line(), c.column());
3104             if (previousNonSpace.isValid() && previousNonSpace.line() == c1.line()) {
3105                 c1 = KTextEditor::Cursor(previousNonSpace.line(), previousNonSpace.column() + 1);
3106             } else if (startedOnSpace || swallowCarriageReturnAtEndOfLine) {
3107                 c1 = KTextEditor::Cursor(c1.line(), 0);
3108             }
3109         }
3110     }
3111 
3112     return Range(c1, c2, !swallowCarriageReturnAtEndOfLine ? InclusiveMotion : ExclusiveMotion);
3113 }
3114 
3115 Range NormalViMode::textObjectInnerWORD()
3116 {
3117     KTextEditor::Cursor c(m_view->cursorPosition());
3118 
3119     KTextEditor::Cursor c1 = findPrevWORDStart(c.line(), c.column() + 1, true);
3120     if (!c1.isValid()) {
3121         c1 = KTextEditor::Cursor(0, 0);
3122     }
3123     KTextEditor::Cursor c2(c);
3124 
3125     for (int i = 0; i < getCount(); i++) {
3126         c2 = findWORDEnd(c2.line(), c2.column(), true);
3127     }
3128 
3129     if (!c2.isValid()) {
3130         c2 = doc()->documentEnd();
3131     }
3132 
3133     // sanity check
3134     if (c1.line() != c2.line() || c1.column() > c2.column()) {
3135         return Range::invalid();
3136     }
3137     return Range(c1, c2, InclusiveMotion);
3138 }
3139 
3140 KTextEditor::Cursor NormalViMode::findSentenceStart()
3141 {
3142     KTextEditor::Cursor c(m_view->cursorPosition());
3143     int linenum = c.line();
3144     int column = c.column();
3145     int prev = column;
3146 
3147     for (int i = linenum; i >= 0; i--) {
3148         const QString &line = doc()->line(i);
3149         const int lineLength = line.size();
3150         if (i != linenum) {
3151             column = lineLength;
3152         }
3153 
3154         // An empty line is the end of a paragraph.
3155         if (line.isEmpty()) {
3156             return KTextEditor::Cursor((i != linenum) ? i + 1 : i, prev);
3157         }
3158 
3159         prev = column;
3160         for (int j = column; j >= 0; j--) {
3161             if (j == lineLength || line.at(j).isSpace()) {
3162                 int lastSpace = j--;
3163                 for (; j >= 0 && QStringLiteral("\"')]").indexOf(line.at(j)) != -1; j--) {
3164                     ;
3165                 }
3166 
3167                 if (j >= 0 && QStringLiteral(".!?").indexOf(line.at(j)) != -1) {
3168                     if (lastSpace == lineLength) {
3169                         // If the line ends with one of .!?, then the sentence starts from the next line.
3170                         return KTextEditor::Cursor(i + 1, 0);
3171                     }
3172 
3173                     return KTextEditor::Cursor(i, prev);
3174                 }
3175                 j = lastSpace;
3176             } else {
3177                 prev = j;
3178             }
3179         }
3180     }
3181 
3182     return KTextEditor::Cursor(0, 0);
3183 }
3184 
3185 KTextEditor::Cursor NormalViMode::findSentenceEnd()
3186 {
3187     KTextEditor::Cursor c(m_view->cursorPosition());
3188     int linenum = c.line();
3189     int column = c.column();
3190     int j = 0;
3191     int prev = 0;
3192 
3193     for (int i = linenum; i < doc()->lines(); i++) {
3194         const QString &line = doc()->line(i);
3195 
3196         // An empty line is the end of a paragraph.
3197         if (line.isEmpty()) {
3198             return KTextEditor::Cursor(linenum, j);
3199         }
3200 
3201         // Iterating over the line to reach any '.', '!', '?'
3202         for (j = column; j < line.size(); j++) {
3203             if (QStringLiteral(".!?").indexOf(line.at(j)) != -1) {
3204                 prev = j++;
3205                 // Skip possible closing characters.
3206                 for (; j < line.size() && QStringLiteral("\"')]").indexOf(line.at(j)) != -1; j++) {
3207                     ;
3208                 }
3209 
3210                 if (j >= line.size()) {
3211                     return KTextEditor::Cursor(i, j - 1);
3212                 }
3213 
3214                 // And hopefully we're done...
3215                 if (line.at(j).isSpace()) {
3216                     return KTextEditor::Cursor(i, j - 1);
3217                 }
3218                 j = prev;
3219             }
3220         }
3221         linenum = i;
3222         prev = column;
3223         column = 0;
3224     }
3225 
3226     return KTextEditor::Cursor(linenum, j - 1);
3227 }
3228 
3229 KTextEditor::Cursor NormalViMode::findParagraphStart()
3230 {
3231     KTextEditor::Cursor c(m_view->cursorPosition());
3232     const bool firstBlank = doc()->line(c.line()).isEmpty();
3233     int prev = c.line();
3234 
3235     for (int i = prev; i >= 0; i--) {
3236         if (doc()->line(i).isEmpty()) {
3237             if (i != prev) {
3238                 prev = i + 1;
3239             }
3240 
3241             /* Skip consecutive empty lines. */
3242             if (firstBlank) {
3243                 i--;
3244                 for (; i >= 0 && doc()->line(i).isEmpty(); i--, prev--) {
3245                     ;
3246                 }
3247             }
3248             return KTextEditor::Cursor(prev, 0);
3249         }
3250     }
3251     return KTextEditor::Cursor(0, 0);
3252 }
3253 
3254 KTextEditor::Cursor NormalViMode::findParagraphEnd()
3255 {
3256     KTextEditor::Cursor c(m_view->cursorPosition());
3257     int prev = c.line();
3258     int lines = doc()->lines();
3259     const bool firstBlank = doc()->line(prev).isEmpty();
3260 
3261     for (int i = prev; i < lines; i++) {
3262         if (doc()->line(i).isEmpty()) {
3263             if (i != prev) {
3264                 prev = i - 1;
3265             }
3266 
3267             /* Skip consecutive empty lines. */
3268             if (firstBlank) {
3269                 i++;
3270                 for (; i < lines && doc()->line(i).isEmpty(); i++, prev++) {
3271                     ;
3272                 }
3273             }
3274             int length = doc()->lineLength(prev);
3275             return KTextEditor::Cursor(prev, (length <= 0) ? 0 : length - 1);
3276         }
3277     }
3278     return doc()->documentEnd();
3279 }
3280 
3281 Range NormalViMode::textObjectInnerSentence()
3282 {
3283     Range r;
3284     KTextEditor::Cursor c1 = findSentenceStart();
3285     KTextEditor::Cursor c2 = findSentenceEnd();
3286     updateCursor(c1);
3287 
3288     r.startLine = c1.line();
3289     r.startColumn = c1.column();
3290     r.endLine = c2.line();
3291     r.endColumn = c2.column();
3292     return r;
3293 }
3294 
3295 Range NormalViMode::textObjectASentence()
3296 {
3297     int i;
3298     Range r = textObjectInnerSentence();
3299     const QString &line = doc()->line(r.endLine);
3300 
3301     // Skip whitespaces and tabs.
3302     for (i = r.endColumn + 1; i < line.size(); i++) {
3303         if (!line.at(i).isSpace()) {
3304             break;
3305         }
3306     }
3307     r.endColumn = i - 1;
3308 
3309     // Remove preceding spaces.
3310     if (r.startColumn != 0) {
3311         if (r.endColumn == line.size() - 1 && !line.at(r.endColumn).isSpace()) {
3312             const QString &line = doc()->line(r.startLine);
3313             for (i = r.startColumn - 1; i >= 0; i--) {
3314                 if (!line.at(i).isSpace()) {
3315                     break;
3316                 }
3317             }
3318             r.startColumn = i + 1;
3319         }
3320     }
3321     return r;
3322 }
3323 
3324 Range NormalViMode::textObjectInnerParagraph()
3325 {
3326     Range r;
3327     KTextEditor::Cursor c1 = findParagraphStart();
3328     KTextEditor::Cursor c2 = findParagraphEnd();
3329     updateCursor(c1);
3330 
3331     r.startLine = c1.line();
3332     r.startColumn = c1.column();
3333     r.endLine = c2.line();
3334     r.endColumn = c2.column();
3335     return r;
3336 }
3337 
3338 Range NormalViMode::textObjectAParagraph()
3339 {
3340     Range r = textObjectInnerParagraph();
3341     int lines = doc()->lines();
3342 
3343     if (r.endLine + 1 < lines) {
3344         // If the next line is empty, remove all subsequent empty lines.
3345         // Otherwise we'll grab the next paragraph.
3346         if (doc()->line(r.endLine + 1).isEmpty()) {
3347             for (int i = r.endLine + 1; i < lines && doc()->line(i).isEmpty(); i++) {
3348                 r.endLine++;
3349             }
3350             r.endColumn = 0;
3351         } else {
3352             KTextEditor::Cursor prev = m_view->cursorPosition();
3353             KTextEditor::Cursor c(r.endLine + 1, 0);
3354             updateCursor(c);
3355             c = findParagraphEnd();
3356             updateCursor(prev);
3357             r.endLine = c.line();
3358             r.endColumn = c.column();
3359         }
3360     } else if (doc()->lineLength(r.startLine) > 0) {
3361         // We went too far, but maybe we can grab previous empty lines.
3362         for (int i = r.startLine - 1; i >= 0 && doc()->line(i).isEmpty(); i--) {
3363             r.startLine--;
3364         }
3365         r.startColumn = 0;
3366         updateCursor(KTextEditor::Cursor(r.startLine, r.startColumn));
3367     } else {
3368         // We went too far and we're on empty lines, do nothing.
3369         return Range::invalid();
3370     }
3371     return r;
3372 }
3373 
3374 Range NormalViMode::textObjectAQuoteDouble()
3375 {
3376     return findSurroundingQuotes(QLatin1Char('"'), false);
3377 }
3378 
3379 Range NormalViMode::textObjectInnerQuoteDouble()
3380 {
3381     return findSurroundingQuotes(QLatin1Char('"'), true);
3382 }
3383 
3384 Range NormalViMode::textObjectAQuoteSingle()
3385 {
3386     return findSurroundingQuotes(QLatin1Char('\''), false);
3387 }
3388 
3389 Range NormalViMode::textObjectInnerQuoteSingle()
3390 {
3391     return findSurroundingQuotes(QLatin1Char('\''), true);
3392 }
3393 
3394 Range NormalViMode::textObjectABackQuote()
3395 {
3396     return findSurroundingQuotes(QLatin1Char('`'), false);
3397 }
3398 
3399 Range NormalViMode::textObjectInnerBackQuote()
3400 {
3401     return findSurroundingQuotes(QLatin1Char('`'), true);
3402 }
3403 
3404 Range NormalViMode::textObjectAParen()
3405 {
3406     return findSurroundingBrackets(QLatin1Char('('), QLatin1Char(')'), false, QLatin1Char('('), QLatin1Char(')'));
3407 }
3408 
3409 Range NormalViMode::textObjectInnerParen()
3410 {
3411     return findSurroundingBrackets(QLatin1Char('('), QLatin1Char(')'), true, QLatin1Char('('), QLatin1Char(')'));
3412 }
3413 
3414 Range NormalViMode::textObjectABracket()
3415 {
3416     return findSurroundingBrackets(QLatin1Char('['), QLatin1Char(']'), false, QLatin1Char('['), QLatin1Char(']'));
3417 }
3418 
3419 Range NormalViMode::textObjectInnerBracket()
3420 {
3421     return findSurroundingBrackets(QLatin1Char('['), QLatin1Char(']'), true, QLatin1Char('['), QLatin1Char(']'));
3422 }
3423 
3424 Range NormalViMode::textObjectACurlyBracket()
3425 {
3426     return findSurroundingBrackets(QLatin1Char('{'), QLatin1Char('}'), false, QLatin1Char('{'), QLatin1Char('}'));
3427 }
3428 
3429 Range NormalViMode::textObjectInnerCurlyBracket()
3430 {
3431     const Range allBetweenCurlyBrackets = findSurroundingBrackets(QLatin1Char('{'), QLatin1Char('}'), true, QLatin1Char('{'), QLatin1Char('}'));
3432     // Emulate the behaviour of vim, which tries to leave the closing bracket on its own line
3433     // if it was originally on a line different to that of the opening bracket.
3434     Range innerCurlyBracket(allBetweenCurlyBrackets);
3435 
3436     if (innerCurlyBracket.startLine != innerCurlyBracket.endLine) {
3437         const bool openingBraceIsLastCharOnLine = innerCurlyBracket.startColumn == doc()->line(innerCurlyBracket.startLine).length();
3438         const bool stuffToDeleteIsAllOnEndLine = openingBraceIsLastCharOnLine && innerCurlyBracket.endLine == innerCurlyBracket.startLine + 1;
3439         const QString textLeadingClosingBracket = doc()->line(innerCurlyBracket.endLine).mid(0, innerCurlyBracket.endColumn + 1);
3440         const bool closingBracketHasLeadingNonWhitespace = !textLeadingClosingBracket.trimmed().isEmpty();
3441         if (stuffToDeleteIsAllOnEndLine) {
3442             if (!closingBracketHasLeadingNonWhitespace) {
3443                 // Nothing there to select - abort.
3444                 return Range::invalid();
3445             } else {
3446                 // Shift the beginning of the range to the start of the line containing the closing bracket.
3447                 innerCurlyBracket.startLine++;
3448                 innerCurlyBracket.startColumn = 0;
3449             }
3450         } else {
3451             if (openingBraceIsLastCharOnLine && !closingBracketHasLeadingNonWhitespace) {
3452                 innerCurlyBracket.startLine++;
3453                 innerCurlyBracket.startColumn = 0;
3454                 m_lastMotionWasLinewiseInnerBlock = true;
3455             }
3456             {
3457                 // The line containing the end bracket is left alone if the end bracket is preceded by just whitespace,
3458                 // else we need to delete everything (i.e. end up with "{}")
3459                 if (!closingBracketHasLeadingNonWhitespace) {
3460                     // Shrink the endpoint of the range so that it ends at the end of the line above,
3461                     // leaving the closing bracket on its own line.
3462                     innerCurlyBracket.endLine--;
3463                     innerCurlyBracket.endColumn = doc()->line(innerCurlyBracket.endLine).length();
3464                 }
3465             }
3466         }
3467     }
3468     return innerCurlyBracket;
3469 }
3470 
3471 Range NormalViMode::textObjectAInequalitySign()
3472 {
3473     return findSurroundingBrackets(QLatin1Char('<'), QLatin1Char('>'), false, QLatin1Char('<'), QLatin1Char('>'));
3474 }
3475 
3476 Range NormalViMode::textObjectInnerInequalitySign()
3477 {
3478     return findSurroundingBrackets(QLatin1Char('<'), QLatin1Char('>'), true, QLatin1Char('<'), QLatin1Char('>'));
3479 }
3480 
3481 Range NormalViMode::textObjectAComma()
3482 {
3483     return textObjectComma(false);
3484 }
3485 
3486 Range NormalViMode::textObjectInnerComma()
3487 {
3488     return textObjectComma(true);
3489 }
3490 
3491 QRegularExpression NormalViMode::generateMatchingItemRegex() const
3492 {
3493     QString pattern(QStringLiteral("\\[|\\]|\\{|\\}|\\(|\\)|"));
3494 
3495     for (QString s : std::as_const(m_matchingItems)) {
3496         if (s.startsWith(QLatin1Char('-'))) {
3497             s.remove(0, 1);
3498         }
3499         s.replace(QLatin1Char('*'), QStringLiteral("\\*"));
3500         s.replace(QLatin1Char('+'), QStringLiteral("\\+"));
3501         s.replace(QLatin1Char('['), QStringLiteral("\\["));
3502         s.replace(QLatin1Char(']'), QStringLiteral("\\]"));
3503         s.replace(QLatin1Char('('), QStringLiteral("\\("));
3504         s.replace(QLatin1Char(')'), QStringLiteral("\\)"));
3505         s.replace(QLatin1Char('{'), QStringLiteral("\\{"));
3506         s.replace(QLatin1Char('}'), QStringLiteral("\\}"));
3507 
3508         s.append(QLatin1Char('|'));
3509         pattern.append(s);
3510     }
3511     // remove extra "|" at the end
3512     pattern.chop(1);
3513 
3514     return QRegularExpression(pattern);
3515 }
3516 
3517 // returns the operation mode that should be used. this is decided by using the following heuristic:
3518 // 1. if we're in visual block mode, it should be Block
3519 // 2. if we're in visual line mode OR the range spans several lines, it should be LineWise
3520 // 3. if neither of these is true, CharWise is returned
3521 // 4. there are some motion that makes all operator charwise, if we have one of them mode will be CharWise
3522 OperationMode NormalViMode::getOperationMode() const
3523 {
3524     OperationMode m = CharWise;
3525 
3526     if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualBlockMode) {
3527         m = Block;
3528     } else if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualLineMode
3529                || (m_commandRange.startLine != m_commandRange.endLine && m_viInputModeManager->getCurrentViMode() != ViMode::VisualMode)) {
3530         m = LineWise;
3531     }
3532 
3533     if (m_commandWithMotion && !m_linewiseCommand) {
3534         m = CharWise;
3535     }
3536 
3537     if (m_lastMotionWasLinewiseInnerBlock) {
3538         m = LineWise;
3539     }
3540 
3541     return m;
3542 }
3543 
3544 bool NormalViMode::paste(PasteLocation pasteLocation, bool isgPaste, bool isIndentedPaste)
3545 {
3546     KTextEditor::Cursor pasteAt(m_view->cursorPosition());
3547     KTextEditor::Cursor cursorAfterPaste = pasteAt;
3548     QChar reg = getChosenRegister(UnnamedRegister);
3549 
3550     OperationMode m = getRegisterFlag(reg);
3551     QString textToInsert = getRegisterContent(reg);
3552     const bool isTextMultiLine = textToInsert.count(QLatin1Char('\n')) > 0;
3553 
3554     // In temporary normal mode, p/P act as gp/gP.
3555     isgPaste |= m_viInputModeManager->getTemporaryNormalMode();
3556 
3557     if (textToInsert.isEmpty()) {
3558         error(i18n("Nothing in register %1", reg.toLower()));
3559         return false;
3560     }
3561 
3562     if (getCount() > 1) {
3563         textToInsert = textToInsert.repeated(getCount()); // FIXME: does this make sense for blocks?
3564     }
3565 
3566     if (m == LineWise) {
3567         pasteAt.setColumn(0);
3568         if (isIndentedPaste) {
3569             // Note that this does indeed work if there is no non-whitespace on the current line or if
3570             // the line is empty!
3571             static const QRegularExpression nonWhitespaceRegex(QStringLiteral("[^\\s]"));
3572             const QString pasteLineString = doc()->line(pasteAt.line());
3573             const QString leadingWhiteSpaceOnCurrentLine = pasteLineString.mid(0, pasteLineString.indexOf(nonWhitespaceRegex));
3574             const QString leadingWhiteSpaceOnFirstPastedLine = textToInsert.mid(0, textToInsert.indexOf(nonWhitespaceRegex));
3575             // QString has no "left trim" method, bizarrely.
3576             while (textToInsert[0].isSpace()) {
3577                 textToInsert = textToInsert.mid(1);
3578             }
3579             textToInsert.prepend(leadingWhiteSpaceOnCurrentLine);
3580             // Remove the last \n, temporarily: we're going to alter the indentation of each pasted line
3581             // by doing a search and replace on '\n's, but don't want to alter this one.
3582             textToInsert.chop(1);
3583             textToInsert.replace(QLatin1Char('\n') + leadingWhiteSpaceOnFirstPastedLine, QLatin1Char('\n') + leadingWhiteSpaceOnCurrentLine);
3584             textToInsert.append(QLatin1Char('\n')); // Re-add the temporarily removed last '\n'.
3585         }
3586         if (pasteLocation == AfterCurrentPosition) {
3587             textToInsert.chop(1); // remove the last \n
3588             pasteAt.setColumn(doc()->lineLength(pasteAt.line())); // paste after the current line and ...
3589             textToInsert.prepend(QLatin1Char('\n')); // ... prepend a \n, so the text starts on a new line
3590 
3591             cursorAfterPaste.setLine(cursorAfterPaste.line() + 1);
3592         }
3593         if (isgPaste) {
3594             cursorAfterPaste.setLine(cursorAfterPaste.line() + textToInsert.count(QLatin1Char('\n')));
3595         }
3596     } else {
3597         if (pasteLocation == AfterCurrentPosition) {
3598             // Move cursor forward one before we paste.  The position after the paste must also
3599             // be updated accordingly.
3600             if (getLine(pasteAt.line()).length() > 0) {
3601                 pasteAt.setColumn(pasteAt.column() + 1);
3602             }
3603             cursorAfterPaste = pasteAt;
3604         }
3605         const bool leaveCursorAtStartOfPaste = isTextMultiLine && !isgPaste;
3606         if (!leaveCursorAtStartOfPaste) {
3607             cursorAfterPaste = cursorPosAtEndOfPaste(pasteAt, textToInsert);
3608             if (!isgPaste) {
3609                 cursorAfterPaste.setColumn(cursorAfterPaste.column() - 1);
3610             }
3611         }
3612     }
3613 
3614     doc()->editBegin();
3615     if (m_view->selection()) {
3616         pasteAt = m_view->selectionRange().start();
3617         doc()->removeText(m_view->selectionRange());
3618     }
3619     doc()->insertText(pasteAt, textToInsert, m == Block);
3620     doc()->editEnd();
3621 
3622     if (cursorAfterPaste.line() >= doc()->lines()) {
3623         cursorAfterPaste.setLine(doc()->lines() - 1);
3624     }
3625     updateCursor(cursorAfterPaste);
3626 
3627     return true;
3628 }
3629 
3630 KTextEditor::Cursor NormalViMode::cursorPosAtEndOfPaste(const KTextEditor::Cursor pasteLocation, const QString &pastedText)
3631 {
3632     KTextEditor::Cursor cAfter = pasteLocation;
3633     const int lineCount = pastedText.count(QLatin1Char('\n')) + 1;
3634     if (lineCount == 1) {
3635         cAfter.setColumn(cAfter.column() + pastedText.length());
3636     } else {
3637         const int lastLineLength = pastedText.size() - (pastedText.lastIndexOf(QLatin1Char('\n')) + 1);
3638         cAfter.setColumn(lastLineLength);
3639         cAfter.setLine(cAfter.line() + lineCount - 1);
3640     }
3641     return cAfter;
3642 }
3643 
3644 void NormalViMode::joinLines(unsigned int from, unsigned int to) const
3645 {
3646     // make sure we don't try to join lines past the document end
3647     if (to >= (unsigned int)(doc()->lines())) {
3648         to = doc()->lines() - 1;
3649     }
3650 
3651     // joining one line is a no-op
3652     if (from == to) {
3653         return;
3654     }
3655 
3656     doc()->joinLines(from, to);
3657 }
3658 
3659 void NormalViMode::reformatLines(unsigned int from, unsigned int to) const
3660 {
3661     // BUG #340550: Do not remove empty lines when reformatting
3662     KTextEditor::DocumentPrivate *document = doc();
3663     auto isNonEmptyLine = [](QStringView text) {
3664         for (int i = 0; i < text.length(); ++i) {
3665             if (!text.at(i).isSpace()) {
3666                 return true;
3667             }
3668         }
3669 
3670         return false;
3671     };
3672     for (; from < to; ++from) {
3673         if (isNonEmptyLine(document->line(from))) {
3674             break;
3675         }
3676     }
3677     for (; to > from; --to) {
3678         if (isNonEmptyLine(document->line(to))) {
3679             break;
3680         }
3681     }
3682 
3683     document->editStart();
3684     joinLines(from, to);
3685     document->wrapText(from, to);
3686     document->editEnd();
3687 }
3688 
3689 int NormalViMode::getFirstNonBlank(int line) const
3690 {
3691     if (line < 0) {
3692         line = m_view->cursorPosition().line();
3693     }
3694 
3695     // doc()->plainKateTextLine returns NULL if the line is out of bounds.
3696     Kate::TextLine l = doc()->plainKateTextLine(line);
3697     Q_ASSERT(l);
3698 
3699     int c = l->firstChar();
3700     return (c < 0) ? 0 : c;
3701 }
3702 
3703 // Tries to shrinks toShrink so that it fits tightly around rangeToShrinkTo.
3704 void NormalViMode::shrinkRangeAroundCursor(Range &toShrink, const Range &rangeToShrinkTo) const
3705 {
3706     if (!toShrink.valid || !rangeToShrinkTo.valid) {
3707         return;
3708     }
3709     KTextEditor::Cursor cursorPos = m_view->cursorPosition();
3710     if (rangeToShrinkTo.startLine >= cursorPos.line()) {
3711         if (rangeToShrinkTo.startLine > cursorPos.line()) {
3712             // Does not surround cursor; aborting.
3713             return;
3714         }
3715         Q_ASSERT(rangeToShrinkTo.startLine == cursorPos.line());
3716         if (rangeToShrinkTo.startColumn > cursorPos.column()) {
3717             // Does not surround cursor; aborting.
3718             return;
3719         }
3720     }
3721     if (rangeToShrinkTo.endLine <= cursorPos.line()) {
3722         if (rangeToShrinkTo.endLine < cursorPos.line()) {
3723             // Does not surround cursor; aborting.
3724             return;
3725         }
3726         Q_ASSERT(rangeToShrinkTo.endLine == cursorPos.line());
3727         if (rangeToShrinkTo.endColumn < cursorPos.column()) {
3728             // Does not surround cursor; aborting.
3729             return;
3730         }
3731     }
3732 
3733     if (toShrink.startLine <= rangeToShrinkTo.startLine) {
3734         if (toShrink.startLine < rangeToShrinkTo.startLine) {
3735             toShrink.startLine = rangeToShrinkTo.startLine;
3736             toShrink.startColumn = rangeToShrinkTo.startColumn;
3737         }
3738         Q_ASSERT(toShrink.startLine == rangeToShrinkTo.startLine);
3739         if (toShrink.startColumn < rangeToShrinkTo.startColumn) {
3740             toShrink.startColumn = rangeToShrinkTo.startColumn;
3741         }
3742     }
3743     if (toShrink.endLine >= rangeToShrinkTo.endLine) {
3744         if (toShrink.endLine > rangeToShrinkTo.endLine) {
3745             toShrink.endLine = rangeToShrinkTo.endLine;
3746             toShrink.endColumn = rangeToShrinkTo.endColumn;
3747         }
3748         Q_ASSERT(toShrink.endLine == rangeToShrinkTo.endLine);
3749         if (toShrink.endColumn > rangeToShrinkTo.endColumn) {
3750             toShrink.endColumn = rangeToShrinkTo.endColumn;
3751         }
3752     }
3753 }
3754 
3755 Range NormalViMode::textObjectComma(bool inner) const
3756 {
3757     // Basic algorithm: look left and right of the cursor for all combinations
3758     // of enclosing commas and the various types of brackets, and pick the pair
3759     // closest to the cursor that surrounds the cursor.
3760     Range r(0, 0, m_view->doc()->lines(), m_view->doc()->line(m_view->doc()->lastLine()).length(), InclusiveMotion);
3761 
3762     shrinkRangeAroundCursor(r, findSurroundingQuotes(QLatin1Char(','), inner));
3763     shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('('), QLatin1Char(')'), inner, QLatin1Char('('), QLatin1Char(')')));
3764     shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('{'), QLatin1Char('}'), inner, QLatin1Char('{'), QLatin1Char('}')));
3765     shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char(','), QLatin1Char(')'), inner, QLatin1Char('('), QLatin1Char(')')));
3766     shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char(','), QLatin1Char(']'), inner, QLatin1Char('['), QLatin1Char(']')));
3767     shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char(','), QLatin1Char('}'), inner, QLatin1Char('{'), QLatin1Char('}')));
3768     shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('('), QLatin1Char(','), inner, QLatin1Char('('), QLatin1Char(')')));
3769     shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('['), QLatin1Char(','), inner, QLatin1Char('['), QLatin1Char(']')));
3770     shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('{'), QLatin1Char(','), inner, QLatin1Char('{'), QLatin1Char('}')));
3771     return r;
3772 }
3773 
3774 void NormalViMode::updateYankHighlightAttrib()
3775 {
3776     if (!m_highlightYankAttribute) {
3777         m_highlightYankAttribute = new KTextEditor::Attribute;
3778     }
3779     const QColor &yankedColor = m_view->renderer()->config()->savedLineColor();
3780     m_highlightYankAttribute->setBackground(yankedColor);
3781     KTextEditor::Attribute::Ptr mouseInAttribute(new KTextEditor::Attribute());
3782     mouseInAttribute->setFontBold(true);
3783     m_highlightYankAttribute->setDynamicAttribute(KTextEditor::Attribute::ActivateMouseIn, mouseInAttribute);
3784     m_highlightYankAttribute->dynamicAttribute(KTextEditor::Attribute::ActivateMouseIn)->setBackground(yankedColor);
3785 }
3786 
3787 void NormalViMode::highlightYank(const Range &range, const OperationMode mode)
3788 {
3789     clearYankHighlight();
3790 
3791     // current MovingRange doesn't support block mode selection so split the
3792     // block range into per-line ranges
3793     if (mode == Block) {
3794         for (int i = range.startLine; i <= range.endLine; i++) {
3795             addHighlightYank(KTextEditor::Range(i, range.startColumn, i, range.endColumn));
3796         }
3797     } else {
3798         addHighlightYank(KTextEditor::Range(range.startLine, range.startColumn, range.endLine, range.endColumn));
3799     }
3800 }
3801 
3802 void NormalViMode::addHighlightYank(KTextEditor::Range yankRange)
3803 {
3804     KTextEditor::MovingRange *highlightedYank = m_view->doc()->newMovingRange(yankRange, Kate::TextRange::DoNotExpand);
3805     highlightedYank->setView(m_view); // show only in this view
3806     highlightedYank->setAttributeOnlyForViews(true);
3807     // use z depth defined in moving ranges interface
3808     highlightedYank->setZDepth(-10000.0);
3809     highlightedYank->setAttribute(m_highlightYankAttribute);
3810 
3811     highlightedYankForDocument().insert(highlightedYank);
3812 }
3813 
3814 void NormalViMode::clearYankHighlight()
3815 {
3816     QSet<KTextEditor::MovingRange *> &pHighlightedYanks = highlightedYankForDocument();
3817     qDeleteAll(pHighlightedYanks);
3818     pHighlightedYanks.clear();
3819 }
3820 
3821 void NormalViMode::aboutToDeleteMovingInterfaceContent()
3822 {
3823     QSet<KTextEditor::MovingRange *> &pHighlightedYanks = highlightedYankForDocument();
3824     // Prevent double-deletion in case this NormalMode is deleted.
3825     pHighlightedYanks.clear();
3826 }
3827 
3828 QSet<KTextEditor::MovingRange *> &NormalViMode::highlightedYankForDocument()
3829 {
3830     // Work around the fact that both Normal and Visual mode will have their own m_highlightedYank -
3831     // make Normal's the canonical one.
3832     return m_viInputModeManager->getViNormalMode()->m_highlightedYanks;
3833 }
3834 
3835 bool NormalViMode::waitingForRegisterOrCharToSearch()
3836 {
3837     // r, q, @ are never preceded by operators. There will always be a keys size of 1 for them.
3838     // f, t, F, T can be preceded by a delete/replace/yank/indent operator. size = 2 in that case.
3839     // f, t, F, T can be preceded by 'g' case/formatting operators. size = 3 in that case.
3840     const int keysSize = m_keys.size();
3841     if (keysSize < 1) {
3842         // Just being defensive there.
3843         return false;
3844     }
3845     if (keysSize > 1) {
3846         // Multi-letter operation.
3847         QChar cPrefix = m_keys[0];
3848         if (keysSize == 2) {
3849             // delete/replace/yank/indent operator?
3850             if (cPrefix != QLatin1Char('c') && cPrefix != QLatin1Char('d') && cPrefix != QLatin1Char('y') && cPrefix != QLatin1Char('=')
3851                 && cPrefix != QLatin1Char('>') && cPrefix != QLatin1Char('<')) {
3852                 return false;
3853             }
3854         } else if (keysSize == 3) {
3855             // We need to look deeper. Is it a g motion?
3856             QChar cNextfix = m_keys[1];
3857             if (cPrefix != QLatin1Char('g')
3858                 || (cNextfix != QLatin1Char('U') && cNextfix != QLatin1Char('u') && cNextfix != QLatin1Char('~') && cNextfix != QLatin1Char('q')
3859                     && cNextfix != QLatin1Char('w') && cNextfix != QLatin1Char('@'))) {
3860                 return false;
3861             }
3862         } else {
3863             return false;
3864         }
3865     }
3866 
3867     QChar ch = m_keys[keysSize - 1];
3868     return (ch == QLatin1Char('f') || ch == QLatin1Char('t') || ch == QLatin1Char('F')
3869             || ch == QLatin1Char('T')
3870             // c/d prefix unapplicable for the following cases.
3871             || (keysSize == 1 && (ch == QLatin1Char('r') || ch == QLatin1Char('q') || ch == QLatin1Char('@'))));
3872 }
3873 
3874 void NormalViMode::textInserted(KTextEditor::Document *document, KTextEditor::Range range)
3875 {
3876     if (m_viInputModeManager->view()->viewInputMode() != KTextEditor::View::ViInputMode) {
3877         return;
3878     }
3879 
3880     Q_UNUSED(document)
3881     const bool isInsertReplaceMode =
3882         (m_viInputModeManager->getCurrentViMode() == ViMode::InsertMode || m_viInputModeManager->getCurrentViMode() == ViMode::ReplaceMode);
3883     const bool continuesInsertion = range.start().line() == m_currentChangeEndMarker.line() && range.start().column() == m_currentChangeEndMarker.column();
3884     const bool beginsWithNewline = doc()->text(range).at(0) == QLatin1Char('\n');
3885     if (!continuesInsertion) {
3886         KTextEditor::Cursor newBeginMarkerPos = range.start();
3887         if (beginsWithNewline && !isInsertReplaceMode) {
3888             // Presumably a linewise paste, in which case we ignore the leading '\n'
3889             newBeginMarkerPos = KTextEditor::Cursor(newBeginMarkerPos.line() + 1, 0);
3890         }
3891         m_viInputModeManager->marks()->setStartEditYanked(newBeginMarkerPos);
3892     }
3893     m_viInputModeManager->marks()->setLastChange(range.start());
3894     KTextEditor::Cursor editEndMarker = range.end();
3895     if (!isInsertReplaceMode) {
3896         editEndMarker.setColumn(editEndMarker.column() - 1);
3897     }
3898     m_viInputModeManager->marks()->setFinishEditYanked(editEndMarker);
3899     m_currentChangeEndMarker = range.end();
3900     if (m_isUndo) {
3901         const bool addsMultipleLines = range.start().line() != range.end().line();
3902         m_viInputModeManager->marks()->setStartEditYanked(KTextEditor::Cursor(m_viInputModeManager->marks()->getStartEditYanked().line(), 0));
3903         if (addsMultipleLines) {
3904             m_viInputModeManager->marks()->setFinishEditYanked(KTextEditor::Cursor(m_viInputModeManager->marks()->getFinishEditYanked().line() + 1, 0));
3905             m_viInputModeManager->marks()->setLastChange(KTextEditor::Cursor(m_viInputModeManager->marks()->getLastChange().line() + 1, 0));
3906         } else {
3907             m_viInputModeManager->marks()->setFinishEditYanked(KTextEditor::Cursor(m_viInputModeManager->marks()->getFinishEditYanked().line(), 0));
3908             m_viInputModeManager->marks()->setLastChange(KTextEditor::Cursor(m_viInputModeManager->marks()->getLastChange().line(), 0));
3909         }
3910     }
3911 }
3912 
3913 void NormalViMode::textRemoved(KTextEditor::Document *document, KTextEditor::Range range)
3914 {
3915     if (m_viInputModeManager->view()->viewInputMode() != KTextEditor::View::ViInputMode) {
3916         return;
3917     }
3918 
3919     Q_UNUSED(document);
3920     const bool isInsertReplaceMode =
3921         (m_viInputModeManager->getCurrentViMode() == ViMode::InsertMode || m_viInputModeManager->getCurrentViMode() == ViMode::ReplaceMode);
3922     m_viInputModeManager->marks()->setLastChange(range.start());
3923     if (!isInsertReplaceMode) {
3924         // Don't go resetting [ just because we did a Ctrl-h!
3925         m_viInputModeManager->marks()->setStartEditYanked(range.start());
3926     } else {
3927         // Don't go disrupting our continued insertion just because we did a Ctrl-h!
3928         m_currentChangeEndMarker = range.start();
3929     }
3930     m_viInputModeManager->marks()->setFinishEditYanked(range.start());
3931     if (m_isUndo) {
3932         // Slavishly follow Vim's weird rules: if an undo removes several lines, then all markers should
3933         // be at the beginning of the line after the last line removed, else they should at the beginning
3934         // of the line above that.
3935         const int markerLineAdjustment = (range.start().line() != range.end().line()) ? 1 : 0;
3936         m_viInputModeManager->marks()->setStartEditYanked(
3937             KTextEditor::Cursor(m_viInputModeManager->marks()->getStartEditYanked().line() + markerLineAdjustment, 0));
3938         m_viInputModeManager->marks()->setFinishEditYanked(
3939             KTextEditor::Cursor(m_viInputModeManager->marks()->getFinishEditYanked().line() + markerLineAdjustment, 0));
3940         m_viInputModeManager->marks()->setLastChange(KTextEditor::Cursor(m_viInputModeManager->marks()->getLastChange().line() + markerLineAdjustment, 0));
3941     }
3942 }
3943 
3944 void NormalViMode::undoBeginning()
3945 {
3946     m_isUndo = true;
3947 }
3948 
3949 void NormalViMode::undoEnded()
3950 {
3951     m_isUndo = false;
3952 }
3953 
3954 bool NormalViMode::executeKateCommand(const QString &command)
3955 {
3956     KTextEditor::Command *p = KateCmd::self()->queryCommand(command);
3957 
3958     if (!p) {
3959         return false;
3960     }
3961 
3962     QString msg;
3963     return p->exec(m_view, command, msg);
3964 }
3965 
3966 #define ADDCMD(STR, FUNC, FLGS) Command(QStringLiteral(STR), &NormalViMode::FUNC, FLGS)
3967 
3968 #define ADDMOTION(STR, FUNC, FLGS) Motion(QStringLiteral(STR), &NormalViMode::FUNC, FLGS)
3969 
3970 const std::vector<Command> &NormalViMode::commands()
3971 {
3972     // init once, is expensive
3973     static const std::vector<Command> global{
3974         ADDCMD("a", commandEnterInsertModeAppend, IS_CHANGE),
3975         ADDCMD("A", commandEnterInsertModeAppendEOL, IS_CHANGE),
3976         ADDCMD("i", commandEnterInsertMode, IS_CHANGE),
3977         ADDCMD("<insert>", commandEnterInsertMode, IS_CHANGE),
3978         ADDCMD("I", commandEnterInsertModeBeforeFirstNonBlankInLine, IS_CHANGE),
3979         ADDCMD("gi", commandEnterInsertModeLast, IS_CHANGE),
3980         ADDCMD("v", commandEnterVisualMode, 0),
3981         ADDCMD("V", commandEnterVisualLineMode, 0),
3982         ADDCMD("<c-v>", commandEnterVisualBlockMode, 0),
3983         ADDCMD("gv", commandReselectVisual, SHOULD_NOT_RESET),
3984         ADDCMD("o", commandOpenNewLineUnder, IS_CHANGE),
3985         ADDCMD("O", commandOpenNewLineOver, IS_CHANGE),
3986         ADDCMD("J", commandJoinLines, IS_CHANGE),
3987         ADDCMD("c", commandChange, IS_CHANGE | NEEDS_MOTION),
3988         ADDCMD("C", commandChangeToEOL, IS_CHANGE),
3989         ADDCMD("cc", commandChangeLine, IS_CHANGE),
3990         ADDCMD("s", commandSubstituteChar, IS_CHANGE),
3991         ADDCMD("S", commandSubstituteLine, IS_CHANGE),
3992         ADDCMD("dd", commandDeleteLine, IS_CHANGE),
3993         ADDCMD("d", commandDelete, IS_CHANGE | NEEDS_MOTION),
3994         ADDCMD("D", commandDeleteToEOL, IS_CHANGE),
3995         ADDCMD("x", commandDeleteChar, IS_CHANGE),
3996         ADDCMD("<delete>", commandDeleteChar, IS_CHANGE),
3997         ADDCMD("X", commandDeleteCharBackward, IS_CHANGE),
3998         ADDCMD("gu", commandMakeLowercase, IS_CHANGE | NEEDS_MOTION),
3999         ADDCMD("guu", commandMakeLowercaseLine, IS_CHANGE),
4000         ADDCMD("gU", commandMakeUppercase, IS_CHANGE | NEEDS_MOTION),
4001         ADDCMD("gUU", commandMakeUppercaseLine, IS_CHANGE),
4002         ADDCMD("y", commandYank, NEEDS_MOTION),
4003         ADDCMD("yy", commandYankLine, 0),
4004         ADDCMD("Y", commandYankToEOL, 0),
4005         ADDCMD("p", commandPaste, IS_CHANGE),
4006         ADDCMD("P", commandPasteBefore, IS_CHANGE),
4007         ADDCMD("gp", commandgPaste, IS_CHANGE),
4008         ADDCMD("gP", commandgPasteBefore, IS_CHANGE),
4009         ADDCMD("]p", commandIndentedPaste, IS_CHANGE),
4010         ADDCMD("[p", commandIndentedPasteBefore, IS_CHANGE),
4011         ADDCMD("r.", commandReplaceCharacter, IS_CHANGE | REGEX_PATTERN),
4012         ADDCMD("R", commandEnterReplaceMode, IS_CHANGE),
4013         ADDCMD(":", commandSwitchToCmdLine, 0),
4014         ADDCMD("u", commandUndo, 0),
4015         ADDCMD("<c-r>", commandRedo, 0),
4016         ADDCMD("U", commandRedo, 0),
4017         ADDCMD("m.", commandSetMark, REGEX_PATTERN),
4018         ADDCMD(">>", commandIndentLine, IS_CHANGE),
4019         ADDCMD("<<", commandUnindentLine, IS_CHANGE),
4020         ADDCMD(">", commandIndentLines, IS_CHANGE | NEEDS_MOTION),
4021         ADDCMD("<", commandUnindentLines, IS_CHANGE | NEEDS_MOTION),
4022         ADDCMD("<c-f>", commandScrollPageDown, 0),
4023         ADDCMD("<pagedown>", commandScrollPageDown, 0),
4024         ADDCMD("<c-b>", commandScrollPageUp, 0),
4025         ADDCMD("<pageup>", commandScrollPageUp, 0),
4026         ADDCMD("<c-u>", commandScrollHalfPageUp, 0),
4027         ADDCMD("<c-d>", commandScrollHalfPageDown, 0),
4028         ADDCMD("z.", commandCenterViewOnNonBlank, 0),
4029         ADDCMD("zz", commandCenterViewOnCursor, 0),
4030         ADDCMD("z<return>", commandTopViewOnNonBlank, 0),
4031         ADDCMD("zt", commandTopViewOnCursor, 0),
4032         ADDCMD("z-", commandBottomViewOnNonBlank, 0),
4033         ADDCMD("zb", commandBottomViewOnCursor, 0),
4034         ADDCMD("ga", commandPrintCharacterCode, SHOULD_NOT_RESET),
4035         ADDCMD(".", commandRepeatLastChange, 0),
4036         ADDCMD("==", commandAlignLine, IS_CHANGE),
4037         ADDCMD("=", commandAlignLines, IS_CHANGE | NEEDS_MOTION),
4038         ADDCMD("~", commandChangeCase, IS_CHANGE),
4039         ADDCMD("g~", commandChangeCaseRange, IS_CHANGE | NEEDS_MOTION),
4040         ADDCMD("g~~", commandChangeCaseLine, IS_CHANGE),
4041         ADDCMD("<c-a>", commandAddToNumber, IS_CHANGE),
4042         ADDCMD("<c-x>", commandSubtractFromNumber, IS_CHANGE),
4043         ADDCMD("<c-o>", commandGoToPrevJump, CAN_LAND_INSIDE_FOLDING_RANGE),
4044         ADDCMD("<c-i>", commandGoToNextJump, CAN_LAND_INSIDE_FOLDING_RANGE),
4045 
4046         ADDCMD("<c-w>h", commandSwitchToLeftView, 0),
4047         ADDCMD("<c-w><c-h>", commandSwitchToLeftView, 0),
4048         ADDCMD("<c-w><left>", commandSwitchToLeftView, 0),
4049         ADDCMD("<c-w>j", commandSwitchToDownView, 0),
4050         ADDCMD("<c-w><c-j>", commandSwitchToDownView, 0),
4051         ADDCMD("<c-w><down>", commandSwitchToDownView, 0),
4052         ADDCMD("<c-w>k", commandSwitchToUpView, 0),
4053         ADDCMD("<c-w><c-k>", commandSwitchToUpView, 0),
4054         ADDCMD("<c-w><up>", commandSwitchToUpView, 0),
4055         ADDCMD("<c-w>l", commandSwitchToRightView, 0),
4056         ADDCMD("<c-w><c-l>", commandSwitchToRightView, 0),
4057         ADDCMD("<c-w><right>", commandSwitchToRightView, 0),
4058         ADDCMD("<c-w>w", commandSwitchToNextView, 0),
4059         ADDCMD("<c-w><c-w>", commandSwitchToNextView, 0),
4060 
4061         ADDCMD("<c-w>s", commandSplitHoriz, 0),
4062         ADDCMD("<c-w>S", commandSplitHoriz, 0),
4063         ADDCMD("<c-w><c-s>", commandSplitHoriz, 0),
4064         ADDCMD("<c-w>v", commandSplitVert, 0),
4065         ADDCMD("<c-w><c-v>", commandSplitVert, 0),
4066         ADDCMD("<c-w>c", commandCloseView, 0),
4067 
4068         ADDCMD("gt", commandSwitchToNextTab, 0),
4069         ADDCMD("gT", commandSwitchToPrevTab, 0),
4070 
4071         ADDCMD("gqq", commandFormatLine, IS_CHANGE),
4072         ADDCMD("gq", commandFormatLines, IS_CHANGE | NEEDS_MOTION),
4073 
4074         ADDCMD("zo", commandExpandLocal, 0),
4075         ADDCMD("zc", commandCollapseLocal, 0),
4076         ADDCMD("za", commandToggleRegionVisibility, 0),
4077         ADDCMD("zr", commandExpandAll, 0),
4078         ADDCMD("zm", commandCollapseToplevelNodes, 0),
4079 
4080         ADDCMD("q.", commandStartRecordingMacro, REGEX_PATTERN),
4081         ADDCMD("@.", commandReplayMacro, REGEX_PATTERN),
4082 
4083         ADDCMD("ZZ", commandCloseWrite, 0),
4084         ADDCMD("ZQ", commandCloseNocheck, 0),
4085     };
4086     return global;
4087 }
4088 
4089 const std::vector<Motion> &NormalViMode::motions()
4090 {
4091     // init once, is expensive
4092     static const std::vector<Motion> global{
4093         // regular motions
4094         ADDMOTION("h", motionLeft, 0),
4095         ADDMOTION("<left>", motionLeft, 0),
4096         ADDMOTION("<backspace>", motionLeft, 0),
4097         ADDMOTION("j", motionDown, 0),
4098         ADDMOTION("<down>", motionDown, 0),
4099         ADDMOTION("<enter>", motionDownToFirstNonBlank, 0),
4100         ADDMOTION("<return>", motionDownToFirstNonBlank, 0),
4101         ADDMOTION("k", motionUp, 0),
4102         ADDMOTION("<up>", motionUp, 0),
4103         ADDMOTION("-", motionUpToFirstNonBlank, 0),
4104         ADDMOTION("+", motionDownToFirstNonBlank, 0),
4105         ADDMOTION("l", motionRight, 0),
4106         ADDMOTION("<right>", motionRight, 0),
4107         ADDMOTION(" ", motionRight, 0),
4108         ADDMOTION("$", motionToEOL, 0),
4109         ADDMOTION("<end>", motionToEOL, 0),
4110         ADDMOTION("0", motionToColumn0, 0),
4111         ADDMOTION("<home>", motionToColumn0, 0),
4112         ADDMOTION("^", motionToFirstCharacterOfLine, 0),
4113         ADDMOTION("f.", motionFindChar, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
4114         ADDMOTION("F.", motionFindCharBackward, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
4115         ADDMOTION("t.", motionToChar, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
4116         ADDMOTION("T.", motionToCharBackward, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
4117         ADDMOTION(";", motionRepeatlastTF, CAN_LAND_INSIDE_FOLDING_RANGE),
4118         ADDMOTION(",", motionRepeatlastTFBackward, CAN_LAND_INSIDE_FOLDING_RANGE),
4119         ADDMOTION("n", motionFindNext, CAN_LAND_INSIDE_FOLDING_RANGE),
4120         ADDMOTION("N", motionFindPrev, CAN_LAND_INSIDE_FOLDING_RANGE),
4121         ADDMOTION("gg", motionToLineFirst, 0),
4122         ADDMOTION("G", motionToLineLast, 0),
4123         ADDMOTION("w", motionWordForward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4124         ADDMOTION("W", motionWORDForward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4125         ADDMOTION("<c-right>", motionWordForward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4126         ADDMOTION("<c-left>", motionWordBackward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4127         ADDMOTION("b", motionWordBackward, CAN_LAND_INSIDE_FOLDING_RANGE),
4128         ADDMOTION("B", motionWORDBackward, CAN_LAND_INSIDE_FOLDING_RANGE),
4129         ADDMOTION("e", motionToEndOfWord, CAN_LAND_INSIDE_FOLDING_RANGE),
4130         ADDMOTION("E", motionToEndOfWORD, CAN_LAND_INSIDE_FOLDING_RANGE),
4131         ADDMOTION("ge", motionToEndOfPrevWord, CAN_LAND_INSIDE_FOLDING_RANGE),
4132         ADDMOTION("gE", motionToEndOfPrevWORD, CAN_LAND_INSIDE_FOLDING_RANGE),
4133         ADDMOTION("|", motionToScreenColumn, 0),
4134         ADDMOTION("%", motionToMatchingItem, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4135         ADDMOTION("`[a-zA-Z^><\\.\\[\\]]", motionToMark, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
4136         ADDMOTION("'[a-zA-Z^><]", motionToMarkLine, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
4137         ADDMOTION("[[", motionToPreviousBraceBlockStart, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4138         ADDMOTION("]]", motionToNextBraceBlockStart, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4139         ADDMOTION("[]", motionToPreviousBraceBlockEnd, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4140         ADDMOTION("][", motionToNextBraceBlockEnd, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4141         ADDMOTION("*", motionToNextOccurrence, CAN_LAND_INSIDE_FOLDING_RANGE),
4142         ADDMOTION("#", motionToPrevOccurrence, CAN_LAND_INSIDE_FOLDING_RANGE),
4143         ADDMOTION("H", motionToFirstLineOfWindow, 0),
4144         ADDMOTION("M", motionToMiddleLineOfWindow, 0),
4145         ADDMOTION("L", motionToLastLineOfWindow, 0),
4146         ADDMOTION("gj", motionToNextVisualLine, 0),
4147         ADDMOTION("g<down>", motionToNextVisualLine, 0),
4148         ADDMOTION("gk", motionToPrevVisualLine, 0),
4149         ADDMOTION("g<up>", motionToPrevVisualLine, 0),
4150         ADDMOTION("(", motionToPreviousSentence, CAN_LAND_INSIDE_FOLDING_RANGE),
4151         ADDMOTION(")", motionToNextSentence, CAN_LAND_INSIDE_FOLDING_RANGE),
4152         ADDMOTION("{", motionToBeforeParagraph, CAN_LAND_INSIDE_FOLDING_RANGE),
4153         ADDMOTION("}", motionToAfterParagraph, CAN_LAND_INSIDE_FOLDING_RANGE),
4154 
4155         // text objects
4156         ADDMOTION("iw", textObjectInnerWord, 0),
4157         ADDMOTION("aw", textObjectAWord, IS_NOT_LINEWISE),
4158         ADDMOTION("iW", textObjectInnerWORD, 0),
4159         ADDMOTION("aW", textObjectAWORD, IS_NOT_LINEWISE),
4160         ADDMOTION("is", textObjectInnerSentence, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4161         ADDMOTION("as", textObjectASentence, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4162         ADDMOTION("ip", textObjectInnerParagraph, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4163         ADDMOTION("ap", textObjectAParagraph, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4164         ADDMOTION("i\"", textObjectInnerQuoteDouble, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4165         ADDMOTION("a\"", textObjectAQuoteDouble, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4166         ADDMOTION("i'", textObjectInnerQuoteSingle, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4167         ADDMOTION("a'", textObjectAQuoteSingle, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4168         ADDMOTION("i`", textObjectInnerBackQuote, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4169         ADDMOTION("a`", textObjectABackQuote, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4170         ADDMOTION("i[()b]", textObjectInnerParen, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4171         ADDMOTION("a[()b]", textObjectAParen, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4172         ADDMOTION("i[{}B]", textObjectInnerCurlyBracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4173         ADDMOTION("a[{}B]", textObjectACurlyBracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4174         ADDMOTION("i[><]", textObjectInnerInequalitySign, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4175         ADDMOTION("a[><]", textObjectAInequalitySign, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4176         ADDMOTION("i[\\[\\]]", textObjectInnerBracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4177         ADDMOTION("a[\\[\\]]", textObjectABracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4178         ADDMOTION("i,", textObjectInnerComma, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4179         ADDMOTION("a,", textObjectAComma, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4180 
4181         ADDMOTION("/<enter>", motionToIncrementalSearchMatch, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4182         ADDMOTION("?<enter>", motionToIncrementalSearchMatch, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4183     };
4184     return global;
4185 }
4186 
4187 #include "moc_normalvimode.cpp"