File indexing completed on 2024-05-12 07:53:47

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