1 /*
2     SPDX-FileCopyrightText: 2008-2009 Erlend Hamberg <ehamberg@gmail.com>
3     SPDX-FileCopyrightText: 2008 Evgeniy Ivanov <powerfox@kde.ru>
4     SPDX-FileCopyrightText: 2009 Paul Gideon Dann <pdgiddie@gmail.com>
5     SPDX-FileCopyrightText: 2011 Svyatoslav Kuzmich <svatoslav1@gmail.com>
6     SPDX-FileCopyrightText: 2012-2013 Simon St James <kdedevel@etotheipiplusone.com>
7 
8     SPDX-License-Identifier: LGPL-2.0-or-later
9 */
10 
11 #include "katebuffer.h"
12 #include "katecmd.h"
13 #include "katecompletionwidget.h"
14 #include "kateconfig.h"
15 #include "katedocument.h"
16 #include "kateglobal.h"
17 #include "katepartdebug.h"
18 #include "kateundomanager.h"
19 #include "kateviewhelpers.h"
20 #include "kateviewinternal.h"
21 #include "kateviinputmode.h"
22 #include <ktexteditor/attribute.h>
23 #include <vimode/emulatedcommandbar/emulatedcommandbar.h>
24 #include <vimode/globalstate.h>
25 #include <vimode/history.h>
26 #include <vimode/inputmodemanager.h>
27 #include <vimode/keymapper.h>
28 #include <vimode/keyparser.h>
29 #include <vimode/lastchangerecorder.h>
30 #include <vimode/macrorecorder.h>
31 #include <vimode/marks.h>
32 #include <vimode/modes/insertvimode.h>
33 #include <vimode/modes/normalvimode.h>
34 #include <vimode/modes/replacevimode.h>
35 #include <vimode/modes/visualvimode.h>
36 #include <vimode/registers.h>
37 #include <vimode/searcher.h>
38 
39 #include <KLocalizedString>
40 #include <QApplication>
41 #include <QList>
42 
43 using namespace KateVi;
44 
45 #define ADDCMD(STR, FUNC, FLGS) m_commands.emplace_back(this, QStringLiteral(STR), &NormalViMode::FUNC, FLGS);
46 
47 #define ADDMOTION(STR, FUNC, FLGS) m_motions.emplace_back(this, QStringLiteral(STR), &NormalViMode::FUNC, FLGS);
48 
NormalViMode(InputModeManager * viInputModeManager,KTextEditor::ViewPrivate * view,KateViewInternal * viewInternal)49 NormalViMode::NormalViMode(InputModeManager *viInputModeManager, KTextEditor::ViewPrivate *view, KateViewInternal *viewInternal)
50     : ModeBase()
51 {
52     m_view = view;
53     m_viewInternal = viewInternal;
54     m_viInputModeManager = viInputModeManager;
55     m_stickyColumn = -1;
56     m_lastMotionWasVisualLineUpOrDown = false;
57     m_currentMotionWasVisualLineUpOrDown = false;
58 
59     // FIXME: make configurable
60     m_extraWordCharacters = QString();
61     m_matchingItems[QStringLiteral("/*")] = QStringLiteral("*/");
62     m_matchingItems[QStringLiteral("*/")] = QStringLiteral("-/*");
63 
64     m_matchItemRegex = generateMatchingItemRegex();
65 
66     m_scroll_count_limit = 1000; // Limit of count for scroll commands.
67 
68     initializeCommands();
69     m_pendingResetIsDueToExit = false;
70     m_isRepeatedTFcommand = false;
71     m_lastMotionWasLinewiseInnerBlock = false;
72     m_motionCanChangeWholeVisualModeSelection = false;
73     resetParser(); // initialise with start configuration
74 
75     m_isUndo = false;
76     connect(doc()->undoManager(), &KateUndoManager::undoStart, this, &NormalViMode::undoBeginning);
77     connect(doc()->undoManager(), &KateUndoManager::undoEnd, this, &NormalViMode::undoEnded);
78 
79     updateYankHighlightAttrib();
80     connect(view, &KTextEditor::View::configChanged, this, &NormalViMode::updateYankHighlightAttrib);
81     connect(doc(), &KTextEditor::DocumentPrivate::aboutToInvalidateMovingInterfaceContent, this, &NormalViMode::clearYankHighlight);
82     connect(doc(), &KTextEditor::DocumentPrivate::aboutToDeleteMovingInterfaceContent, this, &NormalViMode::aboutToDeleteMovingInterfaceContent);
83 }
84 
~NormalViMode()85 NormalViMode::~NormalViMode()
86 {
87     qDeleteAll(m_highlightedYanks);
88 }
89 
90 /**
91  * parses a key stroke to check if it's a valid (part of) a command
92  * @return true if a command was completed and executed, false otherwise
93  */
handleKeypress(const QKeyEvent * e)94 bool NormalViMode::handleKeypress(const QKeyEvent *e)
95 {
96     const int keyCode = e->key();
97 
98     // ignore modifier keys alone
99     if (keyCode == Qt::Key_Shift || keyCode == Qt::Key_Control || keyCode == Qt::Key_Alt || keyCode == Qt::Key_Meta) {
100         return false;
101     }
102 
103     clearYankHighlight();
104 
105     if (keyCode == Qt::Key_Escape || (keyCode == Qt::Key_C && e->modifiers() == Qt::ControlModifier)
106         || (keyCode == Qt::Key_BracketLeft && e->modifiers() == Qt::ControlModifier)) {
107         m_viInputModeManager->inputAdapter()->setCaretStyle(KateRenderer::Block);
108         m_pendingResetIsDueToExit = true;
109         // 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,
110         // but behaves as if it's in normal mode. I'm treating the status bar thing as a bug and just exiting
111         // insert mode altogether.
112         m_viInputModeManager->setTemporaryNormalMode(false);
113         reset();
114         return true;
115     }
116 
117     const QChar key = KeyParser::self()->KeyEventToQChar(*e);
118 
119     const QChar lastChar = m_keys.isEmpty() ? QChar::Null : m_keys.at(m_keys.size() - 1);
120     const bool waitingForRegisterOrCharToSearch = this->waitingForRegisterOrCharToSearch();
121 
122     // Use replace caret when reading a character for "r"
123     if (key == QLatin1Char('r') && !waitingForRegisterOrCharToSearch) {
124         m_viInputModeManager->inputAdapter()->setCaretStyle(KateRenderer::Underline);
125     }
126 
127     m_keysVerbatim.append(KeyParser::self()->decodeKeySequence(key));
128 
129     if ((keyCode >= Qt::Key_0 && keyCode <= Qt::Key_9 && lastChar != QLatin1Char('"')) // key 0-9
130         && (m_countTemp != 0 || keyCode != Qt::Key_0) // first digit can't be 0
131         && (!waitingForRegisterOrCharToSearch) // Not in the middle of "find char" motions or replacing char.
132         && e->modifiers() == Qt::NoModifier) {
133         m_countTemp *= 10;
134         m_countTemp += keyCode - Qt::Key_0;
135 
136         return true;
137     } else if (m_countTemp != 0) {
138         m_count = getCount() * m_countTemp;
139         m_countTemp = 0;
140         m_iscounted = true;
141     }
142 
143     m_keys.append(key);
144 
145     if (m_viInputModeManager->macroRecorder()->isRecording() && key == QLatin1Char('q')) {
146         // Need to special case this "finish macro" q, as the "begin macro" q
147         // needs a parameter whereas the finish macro does not.
148         m_viInputModeManager->macroRecorder()->stop();
149         resetParser();
150         return true;
151     }
152 
153     if ((key == QLatin1Char('/') || key == QLatin1Char('?')) && !waitingForRegisterOrCharToSearch) {
154         // Special case for "/" and "?": these should be motions, but this is complicated by
155         // the fact that the user must interact with the search bar before the range of the
156         // motion can be determined.
157         // We hack around this by showing the search bar immediately, and, when the user has
158         // finished interacting with it, have the search bar send a "synthetic" keypresses
159         // that will either abort everything (if the search was aborted) or "complete" the motion
160         // otherwise.
161         m_positionWhenIncrementalSearchBegan = m_view->cursorPosition();
162         if (key == QLatin1Char('/')) {
163             commandSearchForward();
164         } else {
165             commandSearchBackward();
166         }
167         return true;
168     }
169 
170     // Special case: "cw" and "cW" work the same as "ce" and "cE" if the cursor is
171     // on a non-blank.  This is because Vim interprets "cw" as change-word, and a
172     // word does not include the following white space. (:help cw in vim)
173     if ((m_keys == QLatin1String("cw") || m_keys == QLatin1String("cW")) && !getCharUnderCursor().isSpace()) {
174         // Special case of the special case: :-)
175         // If the cursor is at the end of the current word rewrite to "cl"
176         const bool isWORD = (m_keys.at(1) == QLatin1Char('W'));
177         const KTextEditor::Cursor currentPosition(m_view->cursorPosition());
178         const KTextEditor::Cursor endOfWordOrWORD = (isWORD ? findWORDEnd(currentPosition.line(), currentPosition.column() - 1, true)
179                                                             : findWordEnd(currentPosition.line(), currentPosition.column() - 1, true));
180 
181         if (currentPosition == endOfWordOrWORD) {
182             m_keys = QStringLiteral("cl");
183         } else {
184             if (isWORD) {
185                 m_keys = QStringLiteral("cE");
186             } else {
187                 m_keys = QStringLiteral("ce");
188             }
189         }
190     }
191 
192     if (m_keys[0] == Qt::Key_QuoteDbl) {
193         if (m_keys.size() < 2) {
194             return true; // waiting for a register
195         } else {
196             QChar r = m_keys[1].toLower();
197 
198             if ((r >= QLatin1Char('0') && r <= QLatin1Char('9')) || (r >= QLatin1Char('a') && r <= QLatin1Char('z')) || r == QLatin1Char('_')
199                 || r == QLatin1Char('-') || r == QLatin1Char('+') || r == QLatin1Char('*') || r == QLatin1Char('#') || r == QLatin1Char('^')) {
200                 m_register = m_keys[1];
201                 m_keys.clear();
202                 return true;
203             } else {
204                 resetParser();
205                 return true;
206             }
207         }
208     }
209 
210     // if we have any matching commands so far, check which ones still match
211     if (!m_matchingCommands.isEmpty()) {
212         int n = m_matchingCommands.size() - 1;
213 
214         // remove commands not matching anymore
215         for (int i = n; i >= 0; i--) {
216             if (!m_commands.at(m_matchingCommands.at(i)).matches(m_keys)) {
217                 if (m_commands.at(m_matchingCommands.at(i)).needsMotion()) {
218                     // "cache" command needing a motion for later
219                     m_motionOperatorIndex = m_matchingCommands.at(i);
220                 }
221                 m_matchingCommands.remove(i);
222             }
223         }
224 
225         // check if any of the matching commands need a motion/text object, if so
226         // push the current command length to m_awaitingMotionOrTextObject so one
227         // knows where to split the command between the operator and the motion
228         for (int i = 0; i < m_matchingCommands.size(); i++) {
229             if (m_commands.at(m_matchingCommands.at(i)).needsMotion()) {
230                 m_awaitingMotionOrTextObject.push(m_keys.size());
231                 break;
232             }
233         }
234     } else {
235         // go through all registered commands and put possible matches in m_matchingCommands
236         for (size_t i = 0; i < m_commands.size(); i++) {
237             if (m_commands.at(i).matches(m_keys)) {
238                 m_matchingCommands.push_back(i);
239                 if (m_commands.at(i).needsMotion() && m_commands.at(i).pattern().length() == m_keys.size()) {
240                     m_awaitingMotionOrTextObject.push(m_keys.size());
241                 }
242             }
243         }
244     }
245 
246     // this indicates where in the command string one should start looking for a motion command
247     int checkFrom = (m_awaitingMotionOrTextObject.isEmpty() ? 0 : m_awaitingMotionOrTextObject.top());
248 
249     // Use operator-pending caret when reading a motion for an operator
250     // in normal mode. We need to check that we are indeed in normal mode
251     // since visual mode inherits from it.
252     if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode && !m_awaitingMotionOrTextObject.isEmpty()) {
253         m_viInputModeManager->inputAdapter()->setCaretStyle(KateRenderer::Half);
254     }
255 
256     // look for matching motion commands from position 'checkFrom'
257     // FIXME: if checkFrom hasn't changed, only motions whose index is in
258     // m_matchingMotions should be checked
259     bool motionExecuted = false;
260     if (checkFrom < m_keys.size()) {
261         for (size_t i = 0; i < m_motions.size(); i++) {
262             const QString motion = m_keys.mid(checkFrom);
263             if (m_motions.at(i).matches(motion)) {
264                 m_lastMotionWasLinewiseInnerBlock = false;
265                 m_matchingMotions.push_back(i);
266 
267                 // if it matches exact, we have found the motion command to execute
268                 if (m_motions.at(i).matchesExact(motion)) {
269                     m_currentMotionWasVisualLineUpOrDown = false;
270                     motionExecuted = true;
271                     if (checkFrom == 0) {
272                         // no command given before motion, just move the cursor to wherever
273                         // the motion says it should go to
274                         Range r = m_motions.at(i).execute();
275                         m_motionCanChangeWholeVisualModeSelection = m_motions.at(i).canChangeWholeVisualModeSelection();
276 
277                         if (!m_motions.at(i).canLandInsideFoldingRange()) {
278                             // jump over folding regions since we are just moving the cursor
279                             // except for motions that can end up inside ranges (e.g. n/N, f/F, %, #)
280                             int currLine = m_view->cursorPosition().line();
281                             int delta = r.endLine - currLine;
282                             int vline = m_view->textFolding().lineToVisibleLine(currLine);
283                             r.endLine = m_view->textFolding().visibleLineToLine(qMax(vline + delta, 0) /* ensure we have a valid line */);
284                         }
285 
286                         if (r.endLine >= doc()->lines()) {
287                             r.endLine = doc()->lines() - 1;
288                         }
289 
290                         // make sure the position is valid before moving the cursor there
291                         // TODO: can this be simplified? :/
292                         if (r.valid && r.endLine >= 0 && (r.endLine == 0 || r.endLine <= doc()->lines() - 1) && r.endColumn >= 0) {
293                             if (r.endColumn >= doc()->lineLength(r.endLine) && doc()->lineLength(r.endLine) > 0) {
294                                 r.endColumn = doc()->lineLength(r.endLine) - 1;
295                             }
296 
297                             goToPos(r);
298 
299                             // in the case of VisualMode we need to remember the motion commands as well.
300                             if (!m_viInputModeManager->isAnyVisualMode()) {
301                                 m_viInputModeManager->clearCurrentChangeLog();
302                             }
303                         } else {
304                             qCDebug(LOG_KTE) << "Invalid position: (" << r.endLine << "," << r.endColumn << ")";
305                         }
306 
307                         resetParser();
308 
309                         // if normal mode was started by using Ctrl-O in insert mode,
310                         // it's time to go back to insert mode.
311                         if (m_viInputModeManager->getTemporaryNormalMode()) {
312                             startInsertMode();
313                             m_viewInternal->repaint();
314                         }
315 
316                         m_lastMotionWasVisualLineUpOrDown = m_currentMotionWasVisualLineUpOrDown;
317 
318                         break;
319                     } else {
320                         // execute the specified command and supply the position returned from
321                         // the motion
322 
323                         m_commandRange = m_motions.at(i).execute();
324                         m_linewiseCommand = m_motions.at(i).isLineWise();
325 
326                         // if we didn't get an explicit start position, use the current cursor position
327                         if (m_commandRange.startLine == -1) {
328                             KTextEditor::Cursor c(m_view->cursorPosition());
329                             m_commandRange.startLine = c.line();
330                             m_commandRange.startColumn = c.column();
331                         }
332 
333                         // special case: When using the "w" motion in combination with an operator and
334                         // the last word moved over is at the end of a line, the end of that word
335                         // becomes the end of the operated text, not the first word in the next line.
336                         if (m_motions.at(i).pattern() == QLatin1String("w") || m_motions.at(i).pattern() == QLatin1String("W")) {
337                             if (m_commandRange.endLine != m_commandRange.startLine && m_commandRange.endColumn == getFirstNonBlank(m_commandRange.endLine)) {
338                                 m_commandRange.endLine--;
339                                 m_commandRange.endColumn = doc()->lineLength(m_commandRange.endLine);
340                             }
341                         }
342 
343                         m_commandWithMotion = true;
344 
345                         if (m_commandRange.valid) {
346                             executeCommand(&m_commands.at(m_motionOperatorIndex));
347                         } else {
348                             qCDebug(LOG_KTE) << "Invalid range: "
349                                              << "from (" << m_commandRange.startLine << "," << m_commandRange.startColumn << ")"
350                                              << "to (" << m_commandRange.endLine << "," << m_commandRange.endColumn << ")";
351                         }
352 
353                         if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) {
354                             m_viInputModeManager->inputAdapter()->setCaretStyle(KateRenderer::Block);
355                         }
356                         m_commandWithMotion = false;
357                         reset();
358                         break;
359                     }
360                 }
361             }
362         }
363     }
364 
365     if (this->waitingForRegisterOrCharToSearch()) {
366         // If we are waiting for a char to search or a new register,
367         // don't translate next character; we need the actual character so that e.g.
368         // 'ab' is translated to 'fb' if the mappings 'a' -> 'f' and 'b' -> something else
369         // exist.
370         m_viInputModeManager->keyMapper()->setDoNotMapNextKeypress();
371     }
372 
373     if (motionExecuted) {
374         return true;
375     }
376 
377     // if we have only one match, check if it is a perfect match and if so, execute it
378     // if it's not waiting for a motion or a text object
379     if (m_matchingCommands.size() == 1) {
380         if (m_commands.at(m_matchingCommands.at(0)).matchesExact(m_keys) && !m_commands.at(m_matchingCommands.at(0)).needsMotion()) {
381             if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) {
382                 m_viInputModeManager->inputAdapter()->setCaretStyle(KateRenderer::Block);
383             }
384 
385             Command &cmd = m_commands.at(m_matchingCommands.at(0));
386             executeCommand(&cmd);
387 
388             // check if reset() should be called. some commands in visual mode should not end visual mode
389             if (cmd.shouldReset()) {
390                 reset();
391                 m_view->setBlockSelection(false);
392             }
393             resetParser();
394 
395             return true;
396         }
397     } else if (m_matchingCommands.size() == 0 && m_matchingMotions.size() == 0) {
398         resetParser();
399         // A bit ugly:  we haven't made use of the key event,
400         // but don't want "typeable" keypresses (e.g. a, b, 3, etc) to be marked
401         // as unused as they will then be added to the document, but we don't
402         // want to swallow all keys in case this was a shortcut.
403         // So say we made use of it if and only if it was *not* a shortcut.
404         return e->type() != QEvent::ShortcutOverride;
405     }
406 
407     m_matchingMotions.clear();
408     return true; // TODO - need to check this - it's currently required for making tests pass, but seems odd.
409 }
410 
411 /**
412  * (re)set to start configuration. This is done when a command is completed
413  * executed or when a command is aborted
414  */
resetParser()415 void NormalViMode::resetParser()
416 {
417     m_keys.clear();
418     m_keysVerbatim.clear();
419     m_count = 0;
420     m_oneTimeCountOverride = -1;
421     m_iscounted = false;
422     m_countTemp = 0;
423     m_register = QChar::Null;
424     m_findWaitingForChar = false;
425     m_matchingCommands.clear();
426     m_matchingMotions.clear();
427     m_awaitingMotionOrTextObject.clear();
428     m_motionOperatorIndex = 0;
429 
430     m_commandWithMotion = false;
431     m_linewiseCommand = true;
432     m_deleteCommand = false;
433 
434     m_commandShouldKeepSelection = false;
435 
436     m_currentChangeEndMarker = KTextEditor::Cursor::invalid();
437 
438     if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) {
439         m_viInputModeManager->inputAdapter()->setCaretStyle(KateRenderer::Block);
440     }
441 }
442 
443 // reset the command parser
reset()444 void NormalViMode::reset()
445 {
446     resetParser();
447     m_commandRange.startLine = -1;
448     m_commandRange.startColumn = -1;
449 }
450 
beginMonitoringDocumentChanges()451 void NormalViMode::beginMonitoringDocumentChanges()
452 {
453     connect(doc(), &KTextEditor::DocumentPrivate::textInsertedRange, this, &NormalViMode::textInserted);
454     connect(doc(), &KTextEditor::DocumentPrivate::textRemoved, this, &NormalViMode::textRemoved);
455 }
456 
executeCommand(const Command * cmd)457 void NormalViMode::executeCommand(const Command *cmd)
458 {
459     const ViMode originalViMode = m_viInputModeManager->getCurrentViMode();
460 
461     cmd->execute();
462 
463     // if normal mode was started by using Ctrl-O in insert mode,
464     // it's time to go back to insert mode.
465     if (m_viInputModeManager->getTemporaryNormalMode()) {
466         startInsertMode();
467         m_viewInternal->repaint();
468     }
469 
470     // if the command was a change, and it didn't enter insert mode, store the key presses so that
471     // they can be repeated with '.'
472     if (m_viInputModeManager->getCurrentViMode() != ViMode::InsertMode && m_viInputModeManager->getCurrentViMode() != ViMode::ReplaceMode) {
473         if (cmd->isChange() && !m_viInputModeManager->lastChangeRecorder()->isReplaying()) {
474             m_viInputModeManager->storeLastChangeCommand();
475         }
476 
477         // when we transition to visual mode, remember the command in the keys history (V, v, ctrl-v, ...)
478         // this will later result in buffer filled with something like this "Vjj>" which we can use later with repeat "."
479         const bool commandSwitchedToVisualMode = ((originalViMode == ViMode::NormalMode) && m_viInputModeManager->isAnyVisualMode());
480         if (!commandSwitchedToVisualMode) {
481             m_viInputModeManager->clearCurrentChangeLog();
482         }
483     }
484 
485     // make sure the cursor does not end up after the end of the line
486     KTextEditor::Cursor c(m_view->cursorPosition());
487     if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) {
488         int lineLength = doc()->lineLength(c.line());
489 
490         if (c.column() >= lineLength) {
491             if (lineLength == 0) {
492                 c.setColumn(0);
493             } else {
494                 c.setColumn(lineLength - 1);
495             }
496         }
497         updateCursor(c);
498     }
499 }
500 
501 ////////////////////////////////////////////////////////////////////////////////
502 // COMMANDS AND OPERATORS
503 ////////////////////////////////////////////////////////////////////////////////
504 
505 /**
506  * enter insert mode at the cursor position
507  */
508 
commandEnterInsertMode()509 bool NormalViMode::commandEnterInsertMode()
510 {
511     m_stickyColumn = -1;
512     m_viInputModeManager->getViInsertMode()->setCount(getCount());
513     return startInsertMode();
514 }
515 
516 /**
517  * enter insert mode after the current character
518  */
519 
commandEnterInsertModeAppend()520 bool NormalViMode::commandEnterInsertModeAppend()
521 {
522     KTextEditor::Cursor c(m_view->cursorPosition());
523     c.setColumn(c.column() + 1);
524 
525     // if empty line, the cursor should start at column 0
526     if (doc()->lineLength(c.line()) == 0) {
527         c.setColumn(0);
528     }
529 
530     // cursor should never be in a column > number of columns
531     if (c.column() > doc()->lineLength(c.line())) {
532         c.setColumn(doc()->lineLength(c.line()));
533     }
534 
535     updateCursor(c);
536 
537     m_stickyColumn = -1;
538     m_viInputModeManager->getViInsertMode()->setCount(getCount());
539     return startInsertMode();
540 }
541 
542 /**
543  * start insert mode after the last character of the line
544  */
545 
commandEnterInsertModeAppendEOL()546 bool NormalViMode::commandEnterInsertModeAppendEOL()
547 {
548     KTextEditor::Cursor c(m_view->cursorPosition());
549     c.setColumn(doc()->lineLength(c.line()));
550     updateCursor(c);
551 
552     m_stickyColumn = -1;
553     m_viInputModeManager->getViInsertMode()->setCount(getCount());
554     return startInsertMode();
555 }
556 
commandEnterInsertModeBeforeFirstNonBlankInLine()557 bool NormalViMode::commandEnterInsertModeBeforeFirstNonBlankInLine()
558 {
559     KTextEditor::Cursor cursor(m_view->cursorPosition());
560     int c = getFirstNonBlank();
561 
562     cursor.setColumn(c);
563     updateCursor(cursor);
564 
565     m_stickyColumn = -1;
566     m_viInputModeManager->getViInsertMode()->setCount(getCount());
567     return startInsertMode();
568 }
569 
570 /**
571  * enter insert mode at the last insert position
572  */
573 
commandEnterInsertModeLast()574 bool NormalViMode::commandEnterInsertModeLast()
575 {
576     KTextEditor::Cursor c = m_viInputModeManager->marks()->getInsertStopped();
577     if (c.isValid()) {
578         updateCursor(c);
579     }
580 
581     m_stickyColumn = -1;
582     return startInsertMode();
583 }
584 
commandEnterVisualLineMode()585 bool NormalViMode::commandEnterVisualLineMode()
586 {
587     if (m_viInputModeManager->getCurrentViMode() == VisualLineMode) {
588         reset();
589         return true;
590     }
591 
592     return startVisualLineMode();
593 }
594 
commandEnterVisualBlockMode()595 bool NormalViMode::commandEnterVisualBlockMode()
596 {
597     if (m_viInputModeManager->getCurrentViMode() == VisualBlockMode) {
598         reset();
599         return true;
600     }
601 
602     return startVisualBlockMode();
603 }
604 
commandReselectVisual()605 bool NormalViMode::commandReselectVisual()
606 {
607     // start last visual mode and set start = `< and cursor = `>
608     KTextEditor::Cursor c1 = m_viInputModeManager->marks()->getSelectionStart();
609     KTextEditor::Cursor c2 = m_viInputModeManager->marks()->getSelectionFinish();
610 
611     // we should either get two valid cursors or two invalid cursors
612     Q_ASSERT(c1.isValid() == c2.isValid());
613 
614     if (c1.isValid() && c2.isValid()) {
615         m_viInputModeManager->getViVisualMode()->setStart(c1);
616         bool returnValue = false;
617 
618         switch (m_viInputModeManager->getViVisualMode()->getLastVisualMode()) {
619         case ViMode::VisualMode:
620             returnValue = commandEnterVisualMode();
621             break;
622         case ViMode::VisualLineMode:
623             returnValue = commandEnterVisualLineMode();
624             break;
625         case ViMode::VisualBlockMode:
626             returnValue = commandEnterVisualBlockMode();
627             break;
628         default:
629             Q_ASSERT("invalid visual mode");
630         }
631         m_viInputModeManager->getViVisualMode()->goToPos(c2);
632         return returnValue;
633     } else {
634         error(QStringLiteral("No previous visual selection"));
635     }
636 
637     return false;
638 }
639 
commandEnterVisualMode()640 bool NormalViMode::commandEnterVisualMode()
641 {
642     if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualMode) {
643         reset();
644         return true;
645     }
646 
647     return startVisualMode();
648 }
649 
commandToOtherEnd()650 bool NormalViMode::commandToOtherEnd()
651 {
652     if (m_viInputModeManager->isAnyVisualMode()) {
653         m_viInputModeManager->getViVisualMode()->switchStartEnd();
654         return true;
655     }
656 
657     return false;
658 }
659 
commandEnterReplaceMode()660 bool NormalViMode::commandEnterReplaceMode()
661 {
662     m_stickyColumn = -1;
663     m_viInputModeManager->getViReplaceMode()->setCount(getCount());
664     return startReplaceMode();
665 }
666 
commandDeleteLine()667 bool NormalViMode::commandDeleteLine()
668 {
669     KTextEditor::Cursor c(m_view->cursorPosition());
670 
671     Range r;
672 
673     r.startLine = c.line();
674     r.endLine = c.line() + getCount() - 1;
675 
676     int column = c.column();
677 
678     bool ret = deleteRange(r, LineWise);
679 
680     c = m_view->cursorPosition();
681     if (column > doc()->lineLength(c.line()) - 1) {
682         column = doc()->lineLength(c.line()) - 1;
683     }
684     if (column < 0) {
685         column = 0;
686     }
687 
688     if (c.line() > doc()->lines() - 1) {
689         c.setLine(doc()->lines() - 1);
690     }
691 
692     c.setColumn(column);
693     m_stickyColumn = -1;
694     updateCursor(c);
695 
696     m_deleteCommand = true;
697     return ret;
698 }
699 
commandDelete()700 bool NormalViMode::commandDelete()
701 {
702     m_deleteCommand = true;
703     return deleteRange(m_commandRange, getOperationMode());
704 }
705 
commandDeleteToEOL()706 bool NormalViMode::commandDeleteToEOL()
707 {
708     KTextEditor::Cursor c(m_view->cursorPosition());
709     OperationMode m = CharWise;
710 
711     m_commandRange.endColumn = KateVi::EOL;
712     switch (m_viInputModeManager->getCurrentViMode()) {
713     case ViMode::NormalMode:
714         m_commandRange.startLine = c.line();
715         m_commandRange.startColumn = c.column();
716         m_commandRange.endLine = c.line() + getCount() - 1;
717         break;
718     case ViMode::VisualMode:
719     case ViMode::VisualLineMode:
720         m = LineWise;
721         break;
722     case ViMode::VisualBlockMode:
723         m_commandRange.normalize();
724         m = Block;
725         break;
726     default:
727         /* InsertMode and ReplaceMode will never call this method. */
728         Q_ASSERT(false);
729     }
730 
731     bool r = deleteRange(m_commandRange, m);
732 
733     switch (m) {
734     case CharWise:
735         c.setColumn(doc()->lineLength(c.line()) - 1);
736         break;
737     case LineWise:
738         c.setLine(m_commandRange.startLine);
739         c.setColumn(getFirstNonBlank(qMin(doc()->lastLine(), m_commandRange.startLine)));
740         break;
741     case Block:
742         c.setLine(m_commandRange.startLine);
743         c.setColumn(m_commandRange.startColumn - 1);
744         break;
745     }
746 
747     // make sure cursor position is valid after deletion
748     if (c.line() < 0) {
749         c.setLine(0);
750     }
751     if (c.line() > doc()->lastLine()) {
752         c.setLine(doc()->lastLine());
753     }
754     if (c.column() > doc()->lineLength(c.line()) - 1) {
755         c.setColumn(doc()->lineLength(c.line()) - 1);
756     }
757     if (c.column() < 0) {
758         c.setColumn(0);
759     }
760 
761     updateCursor(c);
762 
763     m_deleteCommand = true;
764     return r;
765 }
766 
commandMakeLowercase()767 bool NormalViMode::commandMakeLowercase()
768 {
769     KTextEditor::Cursor c = m_view->cursorPosition();
770 
771     OperationMode m = getOperationMode();
772     QString text = getRange(m_commandRange, m);
773     if (m == LineWise) {
774         text.chop(1); // don't need '\n' at the end;
775     }
776     QString lowerCase = text.toLower();
777 
778     m_commandRange.normalize();
779     KTextEditor::Cursor start(m_commandRange.startLine, m_commandRange.startColumn);
780     KTextEditor::Cursor end(m_commandRange.endLine, m_commandRange.endColumn);
781     KTextEditor::Range range(start, end);
782 
783     doc()->replaceText(range, lowerCase, m == Block);
784 
785     if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) {
786         updateCursor(start);
787     } else {
788         updateCursor(c);
789     }
790 
791     return true;
792 }
793 
commandMakeLowercaseLine()794 bool NormalViMode::commandMakeLowercaseLine()
795 {
796     KTextEditor::Cursor c(m_view->cursorPosition());
797 
798     if (doc()->lineLength(c.line()) == 0) {
799         // Nothing to do.
800         return true;
801     }
802 
803     m_commandRange.startLine = c.line();
804     m_commandRange.endLine = c.line() + getCount() - 1;
805     m_commandRange.startColumn = 0;
806     m_commandRange.endColumn = doc()->lineLength(c.line()) - 1;
807 
808     return commandMakeLowercase();
809 }
810 
commandMakeUppercase()811 bool NormalViMode::commandMakeUppercase()
812 {
813     if (!m_commandRange.valid) {
814         return false;
815     }
816     KTextEditor::Cursor c = m_view->cursorPosition();
817     OperationMode m = getOperationMode();
818     QString text = getRange(m_commandRange, m);
819     if (m == LineWise) {
820         text.chop(1); // don't need '\n' at the end;
821     }
822     QString upperCase = text.toUpper();
823 
824     m_commandRange.normalize();
825     KTextEditor::Cursor start(m_commandRange.startLine, m_commandRange.startColumn);
826     KTextEditor::Cursor end(m_commandRange.endLine, m_commandRange.endColumn);
827     KTextEditor::Range range(start, end);
828 
829     doc()->replaceText(range, upperCase, m == Block);
830     if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) {
831         updateCursor(start);
832     } else {
833         updateCursor(c);
834     }
835 
836     return true;
837 }
838 
commandMakeUppercaseLine()839 bool NormalViMode::commandMakeUppercaseLine()
840 {
841     KTextEditor::Cursor c(m_view->cursorPosition());
842 
843     if (doc()->lineLength(c.line()) == 0) {
844         // Nothing to do.
845         return true;
846     }
847 
848     m_commandRange.startLine = c.line();
849     m_commandRange.endLine = c.line() + getCount() - 1;
850     m_commandRange.startColumn = 0;
851     m_commandRange.endColumn = doc()->lineLength(c.line()) - 1;
852 
853     return commandMakeUppercase();
854 }
855 
commandChangeCase()856 bool NormalViMode::commandChangeCase()
857 {
858     QString text;
859     KTextEditor::Range range;
860     KTextEditor::Cursor c(m_view->cursorPosition());
861 
862     // in visual mode, the range is from start position to end position...
863     if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualMode || m_viInputModeManager->getCurrentViMode() == ViMode::VisualBlockMode) {
864         KTextEditor::Cursor c2 = m_viInputModeManager->getViVisualMode()->getStart();
865 
866         if (c2 > c) {
867             c2.setColumn(c2.column() + 1);
868         } else {
869             c.setColumn(c.column() + 1);
870         }
871 
872         range.setRange(c, c2);
873         // ... in visual line mode, the range is from column 0 on the first line to
874         // the line length of the last line...
875     } else if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualLineMode) {
876         KTextEditor::Cursor c2 = m_viInputModeManager->getViVisualMode()->getStart();
877 
878         if (c2 > c) {
879             c2.setColumn(doc()->lineLength(c2.line()));
880             c.setColumn(0);
881         } else {
882             c.setColumn(doc()->lineLength(c.line()));
883             c2.setColumn(0);
884         }
885 
886         range.setRange(c, c2);
887         // ... and in normal mode the range is from the current position to the
888         // current position + count
889     } else {
890         KTextEditor::Cursor c2 = c;
891         c2.setColumn(c.column() + getCount());
892 
893         if (c2.column() > doc()->lineLength(c.line())) {
894             c2.setColumn(doc()->lineLength(c.line()));
895         }
896 
897         range.setRange(c, c2);
898     }
899 
900     bool block = m_viInputModeManager->getCurrentViMode() == ViMode::VisualBlockMode;
901 
902     // get the text the command should operate on
903     text = doc()->text(range, block);
904 
905     // for every character, switch its case
906     for (int i = 0; i < text.length(); i++) {
907         if (text.at(i).isUpper()) {
908             text[i] = text.at(i).toLower();
909         } else if (text.at(i).isLower()) {
910             text[i] = text.at(i).toUpper();
911         }
912     }
913 
914     // replace the old text with the modified text
915     doc()->replaceText(range, text, block);
916 
917     // in normal mode, move the cursor to the right, in visual mode move the
918     // cursor to the start of the selection
919     if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) {
920         updateCursor(range.end());
921     } else {
922         updateCursor(range.start());
923     }
924 
925     return true;
926 }
927 
commandChangeCaseRange()928 bool NormalViMode::commandChangeCaseRange()
929 {
930     OperationMode m = getOperationMode();
931     QString changedCase = getRange(m_commandRange, m);
932     if (m == LineWise) {
933         changedCase.chop(1); // don't need '\n' at the end;
934     }
935     KTextEditor::Range range = KTextEditor::Range(m_commandRange.startLine, m_commandRange.startColumn, m_commandRange.endLine, m_commandRange.endColumn);
936     // get the text the command should operate on
937     // for every character, switch its case
938     for (int i = 0; i < changedCase.length(); i++) {
939         if (changedCase.at(i).isUpper()) {
940             changedCase[i] = changedCase.at(i).toLower();
941         } else if (changedCase.at(i).isLower()) {
942             changedCase[i] = changedCase.at(i).toUpper();
943         }
944     }
945     doc()->replaceText(range, changedCase, m == Block);
946     return true;
947 }
948 
commandChangeCaseLine()949 bool NormalViMode::commandChangeCaseLine()
950 {
951     KTextEditor::Cursor c(m_view->cursorPosition());
952 
953     if (doc()->lineLength(c.line()) == 0) {
954         // Nothing to do.
955         return true;
956     }
957 
958     m_commandRange.startLine = c.line();
959     m_commandRange.endLine = c.line() + getCount() - 1;
960     m_commandRange.startColumn = 0;
961     m_commandRange.endColumn = doc()->lineLength(c.line()) - 1; // -1 is for excluding '\0'
962 
963     if (!commandChangeCaseRange()) {
964         return false;
965     }
966 
967     KTextEditor::Cursor start(m_commandRange.startLine, m_commandRange.startColumn);
968     if (getCount() > 1) {
969         updateCursor(c);
970     } else {
971         updateCursor(start);
972     }
973     return true;
974 }
975 
commandOpenNewLineUnder()976 bool NormalViMode::commandOpenNewLineUnder()
977 {
978     doc()->setUndoMergeAllEdits(true);
979 
980     KTextEditor::Cursor c(m_view->cursorPosition());
981 
982     c.setColumn(doc()->lineLength(c.line()));
983     updateCursor(c);
984 
985     doc()->newLine(m_view);
986 
987     m_stickyColumn = -1;
988     startInsertMode();
989     m_viInputModeManager->getViInsertMode()->setCount(getCount());
990     m_viInputModeManager->getViInsertMode()->setCountedRepeatsBeginOnNewLine(true);
991 
992     return true;
993 }
994 
commandOpenNewLineOver()995 bool NormalViMode::commandOpenNewLineOver()
996 {
997     doc()->setUndoMergeAllEdits(true);
998 
999     KTextEditor::Cursor c(m_view->cursorPosition());
1000 
1001     if (c.line() == 0) {
1002         doc()->insertLine(0, QString());
1003         c.setColumn(0);
1004         c.setLine(0);
1005         updateCursor(c);
1006     } else {
1007         c.setLine(c.line() - 1);
1008         c.setColumn(getLine(c.line()).length());
1009         updateCursor(c);
1010         doc()->newLine(m_view);
1011     }
1012 
1013     m_stickyColumn = -1;
1014     startInsertMode();
1015     m_viInputModeManager->getViInsertMode()->setCount(getCount());
1016     m_viInputModeManager->getViInsertMode()->setCountedRepeatsBeginOnNewLine(true);
1017 
1018     return true;
1019 }
1020 
commandJoinLines()1021 bool NormalViMode::commandJoinLines()
1022 {
1023     KTextEditor::Cursor c(m_view->cursorPosition());
1024 
1025     unsigned int from = c.line();
1026     unsigned int to = c.line() + ((getCount() == 1) ? 1 : getCount() - 1);
1027 
1028     // if we were given a range of lines, this information overrides the previous
1029     if (m_commandRange.startLine != -1 && m_commandRange.endLine != -1) {
1030         m_commandRange.normalize();
1031         c.setLine(m_commandRange.startLine);
1032         from = m_commandRange.startLine;
1033         to = m_commandRange.endLine;
1034     }
1035 
1036     if (to >= (unsigned int)doc()->lines()) {
1037         return false;
1038     }
1039 
1040     bool nonEmptyLineFound = false;
1041     for (unsigned int lineNum = from; lineNum <= to; lineNum++) {
1042         if (!doc()->line(lineNum).isEmpty()) {
1043             nonEmptyLineFound = true;
1044         }
1045     }
1046 
1047     const int firstNonWhitespaceOnLastLine = doc()->kateTextLine(to)->firstChar();
1048     QString leftTrimmedLastLine;
1049     if (firstNonWhitespaceOnLastLine != -1) {
1050         leftTrimmedLastLine = doc()->line(to).mid(firstNonWhitespaceOnLastLine);
1051     }
1052 
1053     joinLines(from, to);
1054 
1055     if (nonEmptyLineFound && leftTrimmedLastLine.isEmpty()) {
1056         // joinLines won't have added a trailing " ", whereas Vim does - follow suit.
1057         doc()->insertText(KTextEditor::Cursor(from, doc()->lineLength(from)), QStringLiteral(" "));
1058     }
1059 
1060     // Position cursor just before first non-whitesspace character of what was the last line joined.
1061     c.setColumn(doc()->lineLength(from) - leftTrimmedLastLine.length() - 1);
1062     if (c.column() >= 0) {
1063         updateCursor(c);
1064     }
1065 
1066     m_deleteCommand = true;
1067     return true;
1068 }
1069 
commandChange()1070 bool NormalViMode::commandChange()
1071 {
1072     KTextEditor::Cursor c(m_view->cursorPosition());
1073 
1074     OperationMode m = getOperationMode();
1075 
1076     doc()->setUndoMergeAllEdits(true);
1077 
1078     commandDelete();
1079 
1080     if (m == LineWise) {
1081         // if we deleted several lines, insert an empty line and put the cursor there.
1082         doc()->insertLine(m_commandRange.startLine, QString());
1083         c.setLine(m_commandRange.startLine);
1084         c.setColumn(0);
1085     } else if (m == Block) {
1086         // block substitute can be simulated by first deleting the text
1087         // (done above) and then starting block prepend.
1088         return commandPrependToBlock();
1089     } else {
1090         if (m_commandRange.startLine < m_commandRange.endLine) {
1091             c.setLine(m_commandRange.startLine);
1092         }
1093         c.setColumn(m_commandRange.startColumn);
1094     }
1095 
1096     updateCursor(c);
1097     setCount(0); // The count was for the motion, not the insertion.
1098     commandEnterInsertMode();
1099 
1100     // correct indentation level
1101     if (m == LineWise) {
1102         m_view->align();
1103     }
1104 
1105     m_deleteCommand = true;
1106     return true;
1107 }
1108 
commandChangeToEOL()1109 bool NormalViMode::commandChangeToEOL()
1110 {
1111     commandDeleteToEOL();
1112 
1113     if (getOperationMode() == Block) {
1114         return commandPrependToBlock();
1115     }
1116 
1117     m_deleteCommand = true;
1118     return commandEnterInsertModeAppend();
1119 }
1120 
commandChangeLine()1121 bool NormalViMode::commandChangeLine()
1122 {
1123     m_deleteCommand = true;
1124     KTextEditor::Cursor c(m_view->cursorPosition());
1125     c.setColumn(0);
1126     updateCursor(c);
1127 
1128     doc()->setUndoMergeAllEdits(true);
1129 
1130     // if count >= 2 start by deleting the whole lines
1131     if (getCount() >= 2) {
1132         Range r(c.line(), 0, c.line() + getCount() - 2, 0, InclusiveMotion);
1133         deleteRange(r);
1134     }
1135 
1136     // ... then delete the _contents_ of the last line, but keep the line
1137     Range r(c.line(), c.column(), c.line(), doc()->lineLength(c.line()) - 1, InclusiveMotion);
1138     deleteRange(r, CharWise, true);
1139 
1140     // ... then enter insert mode
1141     if (getOperationMode() == Block) {
1142         return commandPrependToBlock();
1143     }
1144     commandEnterInsertModeAppend();
1145 
1146     // correct indentation level
1147     m_view->align();
1148 
1149     return true;
1150 }
1151 
commandSubstituteChar()1152 bool NormalViMode::commandSubstituteChar()
1153 {
1154     if (commandDeleteChar()) {
1155         // The count is only used for deletion of chars; the inserted text is not repeated
1156         setCount(0);
1157         return commandEnterInsertMode();
1158     }
1159 
1160     m_deleteCommand = true;
1161     return false;
1162 }
1163 
commandSubstituteLine()1164 bool NormalViMode::commandSubstituteLine()
1165 {
1166     m_deleteCommand = true;
1167     return commandChangeLine();
1168 }
1169 
commandYank()1170 bool NormalViMode::commandYank()
1171 {
1172     bool r = false;
1173     QString yankedText;
1174 
1175     OperationMode m = getOperationMode();
1176     yankedText = getRange(m_commandRange, m);
1177 
1178     highlightYank(m_commandRange, m);
1179 
1180     QChar chosen_register = getChosenRegister(ZeroRegister);
1181     fillRegister(chosen_register, yankedText, m);
1182     yankToClipBoard(chosen_register, yankedText);
1183 
1184     return r;
1185 }
1186 
commandYankLine()1187 bool NormalViMode::commandYankLine()
1188 {
1189     KTextEditor::Cursor c(m_view->cursorPosition());
1190     QString lines;
1191     int linenum = c.line();
1192 
1193     for (int i = 0; i < getCount(); i++) {
1194         lines.append(getLine(linenum + i) + QLatin1Char('\n'));
1195     }
1196 
1197     Range yankRange(linenum, 0, linenum + getCount() - 1, getLine(linenum + getCount() - 1).length(), InclusiveMotion);
1198     highlightYank(yankRange);
1199 
1200     QChar chosen_register = getChosenRegister(ZeroRegister);
1201     fillRegister(chosen_register, lines, LineWise);
1202     yankToClipBoard(chosen_register, lines);
1203 
1204     return true;
1205 }
1206 
commandYankToEOL()1207 bool NormalViMode::commandYankToEOL()
1208 {
1209     OperationMode m = CharWise;
1210     KTextEditor::Cursor c(m_view->cursorPosition());
1211 
1212     MotionType motion = m_commandRange.motionType;
1213     m_commandRange.endLine = c.line() + getCount() - 1;
1214     m_commandRange.endColumn = doc()->lineLength(m_commandRange.endLine) - 1;
1215     m_commandRange.motionType = InclusiveMotion;
1216 
1217     switch (m_viInputModeManager->getCurrentViMode()) {
1218     case ViMode::NormalMode:
1219         m_commandRange.startLine = c.line();
1220         m_commandRange.startColumn = c.column();
1221         break;
1222     case ViMode::VisualMode:
1223     case ViMode::VisualLineMode:
1224         m = LineWise;
1225         {
1226             VisualViMode *visual = static_cast<VisualViMode *>(this);
1227             visual->setStart(KTextEditor::Cursor(visual->getStart().line(), 0));
1228         }
1229         break;
1230     case ViMode::VisualBlockMode:
1231         m = Block;
1232         break;
1233     default:
1234         /* InsertMode and ReplaceMode will never call this method. */
1235         Q_ASSERT(false);
1236     }
1237 
1238     const QString &yankedText = getRange(m_commandRange, m);
1239     m_commandRange.motionType = motion;
1240     highlightYank(m_commandRange);
1241 
1242     QChar chosen_register = getChosenRegister(ZeroRegister);
1243     fillRegister(chosen_register, yankedText, m);
1244     yankToClipBoard(chosen_register, yankedText);
1245 
1246     return true;
1247 }
1248 
1249 // Insert the text in the given register after the cursor position.
1250 // This is the non-g version of paste, so the cursor will usually
1251 // end up on the last character of the pasted text, unless the text
1252 // was multi-line or linewise in which case it will end up
1253 // on the *first* character of the pasted text(!)
1254 // If linewise, will paste after the current line.
commandPaste()1255 bool NormalViMode::commandPaste()
1256 {
1257     return paste(AfterCurrentPosition, false, false);
1258 }
1259 
1260 // As with commandPaste, except that the text is pasted *at* the cursor position
commandPasteBefore()1261 bool NormalViMode::commandPasteBefore()
1262 {
1263     return paste(AtCurrentPosition, false, false);
1264 }
1265 
1266 // As with commandPaste, except that the cursor will generally be placed *after* the
1267 // last pasted character (assuming the last pasted character is not at  the end of the line).
1268 // If linewise, cursor will be at the beginning of the line *after* the last line of pasted text,
1269 // unless that line is the last line of the document; then it will be placed at the beginning of the
1270 // last line pasted.
commandgPaste()1271 bool NormalViMode::commandgPaste()
1272 {
1273     return paste(AfterCurrentPosition, true, false);
1274 }
1275 
1276 // As with commandgPaste, except that it pastes *at* the current cursor position or, if linewise,
1277 // at the current line.
commandgPasteBefore()1278 bool NormalViMode::commandgPasteBefore()
1279 {
1280     return paste(AtCurrentPosition, true, false);
1281 }
1282 
commandIndentedPaste()1283 bool NormalViMode::commandIndentedPaste()
1284 {
1285     return paste(AfterCurrentPosition, false, true);
1286 }
1287 
commandIndentedPasteBefore()1288 bool NormalViMode::commandIndentedPasteBefore()
1289 {
1290     return paste(AtCurrentPosition, false, true);
1291 }
1292 
commandDeleteChar()1293 bool NormalViMode::commandDeleteChar()
1294 {
1295     KTextEditor::Cursor c(m_view->cursorPosition());
1296     Range r(c.line(), c.column(), c.line(), c.column() + getCount(), ExclusiveMotion);
1297 
1298     if (m_commandRange.startLine != -1 && m_commandRange.startColumn != -1) {
1299         r = m_commandRange;
1300     } else {
1301         if (r.endColumn > doc()->lineLength(r.startLine)) {
1302             r.endColumn = doc()->lineLength(r.startLine);
1303         }
1304     }
1305 
1306     // should delete entire lines if in visual line mode and selection in visual block mode
1307     OperationMode m = CharWise;
1308     if (m_viInputModeManager->getCurrentViMode() == VisualLineMode) {
1309         m = LineWise;
1310     } else if (m_viInputModeManager->getCurrentViMode() == VisualBlockMode) {
1311         m = Block;
1312     }
1313 
1314     m_deleteCommand = true;
1315     return deleteRange(r, m);
1316 }
1317 
commandDeleteCharBackward()1318 bool NormalViMode::commandDeleteCharBackward()
1319 {
1320     KTextEditor::Cursor c(m_view->cursorPosition());
1321 
1322     Range r(c.line(), c.column() - getCount(), c.line(), c.column(), ExclusiveMotion);
1323 
1324     if (m_commandRange.startLine != -1 && m_commandRange.startColumn != -1) {
1325         r = m_commandRange;
1326     } else {
1327         if (r.startColumn < 0) {
1328             r.startColumn = 0;
1329         }
1330     }
1331 
1332     // should delete entire lines if in visual line mode and selection in visual block mode
1333     OperationMode m = CharWise;
1334     if (m_viInputModeManager->getCurrentViMode() == VisualLineMode) {
1335         m = LineWise;
1336     } else if (m_viInputModeManager->getCurrentViMode() == VisualBlockMode) {
1337         m = Block;
1338     }
1339 
1340     m_deleteCommand = true;
1341     return deleteRange(r, m);
1342 }
1343 
commandReplaceCharacter()1344 bool NormalViMode::commandReplaceCharacter()
1345 {
1346     QString key = KeyParser::self()->decodeKeySequence(m_keys.right(1));
1347 
1348     // Filter out some special keys.
1349     const int keyCode = KeyParser::self()->encoded2qt(m_keys.right(1));
1350     switch (keyCode) {
1351     case Qt::Key_Left:
1352     case Qt::Key_Right:
1353     case Qt::Key_Up:
1354     case Qt::Key_Down:
1355     case Qt::Key_Home:
1356     case Qt::Key_End:
1357     case Qt::Key_PageUp:
1358     case Qt::Key_PageDown:
1359     case Qt::Key_Delete:
1360     case Qt::Key_Insert:
1361     case Qt::Key_Backspace:
1362     case Qt::Key_CapsLock:
1363         return true;
1364     case Qt::Key_Return:
1365     case Qt::Key_Enter:
1366         key = QStringLiteral("\n");
1367     }
1368 
1369     bool r;
1370     if (m_viInputModeManager->isAnyVisualMode()) {
1371         OperationMode m = getOperationMode();
1372         QString text = getRange(m_commandRange, m);
1373 
1374         if (m == LineWise) {
1375             text.chop(1); // don't need '\n' at the end;
1376         }
1377 
1378         static const QRegularExpression nonNewlineRegex(QStringLiteral("[^\n]"));
1379         text.replace(nonNewlineRegex, key);
1380 
1381         m_commandRange.normalize();
1382         KTextEditor::Cursor start(m_commandRange.startLine, m_commandRange.startColumn);
1383         KTextEditor::Cursor end(m_commandRange.endLine, m_commandRange.endColumn);
1384         KTextEditor::Range range(start, end);
1385 
1386         r = doc()->replaceText(range, text, m == Block);
1387 
1388     } else {
1389         KTextEditor::Cursor c1(m_view->cursorPosition());
1390         KTextEditor::Cursor c2(m_view->cursorPosition());
1391 
1392         c2.setColumn(c2.column() + getCount());
1393 
1394         if (c2.column() > doc()->lineLength(m_view->cursorPosition().line())) {
1395             return false;
1396         }
1397 
1398         r = doc()->replaceText(KTextEditor::Range(c1, c2), key.repeated(getCount()));
1399         updateCursor(c1);
1400     }
1401     return r;
1402 }
1403 
commandSwitchToCmdLine()1404 bool NormalViMode::commandSwitchToCmdLine()
1405 {
1406     QString initialText;
1407     if (m_viInputModeManager->isAnyVisualMode()) {
1408         // if in visual mode, make command range == visual selection
1409         m_viInputModeManager->getViVisualMode()->saveRangeMarks();
1410         initialText = QStringLiteral("'<,'>");
1411     } else if (getCount() != 1) {
1412         // if a count is given, the range [current line] to [current line] +
1413         // count should be prepended to the command line
1414         initialText = QLatin1String(".,.+") + QString::number(getCount() - 1);
1415     }
1416 
1417     m_viInputModeManager->inputAdapter()->showViModeEmulatedCommandBar();
1418     m_viInputModeManager->inputAdapter()->viModeEmulatedCommandBar()->init(EmulatedCommandBar::Command, initialText);
1419 
1420     m_commandShouldKeepSelection = true;
1421 
1422     return true;
1423 }
1424 
commandSearchBackward()1425 bool NormalViMode::commandSearchBackward()
1426 {
1427     m_viInputModeManager->inputAdapter()->showViModeEmulatedCommandBar();
1428     m_viInputModeManager->inputAdapter()->viModeEmulatedCommandBar()->init(EmulatedCommandBar::SearchBackward);
1429     return true;
1430 }
1431 
commandSearchForward()1432 bool NormalViMode::commandSearchForward()
1433 {
1434     m_viInputModeManager->inputAdapter()->showViModeEmulatedCommandBar();
1435     m_viInputModeManager->inputAdapter()->viModeEmulatedCommandBar()->init(EmulatedCommandBar::SearchForward);
1436     return true;
1437 }
1438 
commandUndo()1439 bool NormalViMode::commandUndo()
1440 {
1441     // See BUG #328277
1442     m_viInputModeManager->clearCurrentChangeLog();
1443 
1444     if (doc()->undoCount() > 0) {
1445         const bool mapped = m_viInputModeManager->keyMapper()->isExecutingMapping();
1446 
1447         if (mapped) {
1448             doc()->editEnd();
1449         }
1450         doc()->undo();
1451         if (mapped) {
1452             doc()->editBegin();
1453         }
1454         if (m_viInputModeManager->isAnyVisualMode()) {
1455             m_viInputModeManager->getViVisualMode()->setStart(KTextEditor::Cursor(-1, -1));
1456             m_view->clearSelection();
1457             startNormalMode();
1458         }
1459         return true;
1460     }
1461     return false;
1462 }
1463 
commandRedo()1464 bool NormalViMode::commandRedo()
1465 {
1466     if (doc()->redoCount() > 0) {
1467         const bool mapped = m_viInputModeManager->keyMapper()->isExecutingMapping();
1468 
1469         if (mapped) {
1470             doc()->editEnd();
1471         }
1472         doc()->redo();
1473         if (mapped) {
1474             doc()->editBegin();
1475         }
1476         if (m_viInputModeManager->isAnyVisualMode()) {
1477             m_viInputModeManager->getViVisualMode()->setStart(KTextEditor::Cursor(-1, -1));
1478             m_view->clearSelection();
1479             startNormalMode();
1480         }
1481         return true;
1482     }
1483     return false;
1484 }
1485 
commandSetMark()1486 bool NormalViMode::commandSetMark()
1487 {
1488     KTextEditor::Cursor c(m_view->cursorPosition());
1489 
1490     QChar mark = m_keys.at(m_keys.size() - 1);
1491     m_viInputModeManager->marks()->setUserMark(mark, c);
1492 
1493     return true;
1494 }
1495 
commandIndentLine()1496 bool NormalViMode::commandIndentLine()
1497 {
1498     KTextEditor::Cursor c(m_view->cursorPosition());
1499 
1500     doc()->indent(KTextEditor::Range(c.line(), 0, c.line() + getCount(), 0), 1);
1501 
1502     return true;
1503 }
1504 
commandUnindentLine()1505 bool NormalViMode::commandUnindentLine()
1506 {
1507     KTextEditor::Cursor c(m_view->cursorPosition());
1508 
1509     doc()->indent(KTextEditor::Range(c.line(), 0, c.line() + getCount(), 0), -1);
1510 
1511     return true;
1512 }
1513 
commandIndentLines()1514 bool NormalViMode::commandIndentLines()
1515 {
1516     const bool downwards = m_commandRange.startLine < m_commandRange.endLine;
1517 
1518     m_commandRange.normalize();
1519 
1520     int line1 = m_commandRange.startLine;
1521     int line2 = m_commandRange.endLine;
1522     int col = getLine(line2).length();
1523     doc()->indent(KTextEditor::Range(line1, 0, line2, col), getCount());
1524 
1525     if (downwards) {
1526         updateCursor(KTextEditor::Cursor(m_commandRange.startLine, m_commandRange.startColumn));
1527     } else {
1528         updateCursor(KTextEditor::Cursor(m_commandRange.endLine, m_commandRange.endColumn));
1529     }
1530     return true;
1531 }
1532 
commandUnindentLines()1533 bool NormalViMode::commandUnindentLines()
1534 {
1535     const bool downwards = m_commandRange.startLine < m_commandRange.endLine;
1536 
1537     m_commandRange.normalize();
1538 
1539     int line1 = m_commandRange.startLine;
1540     int line2 = m_commandRange.endLine;
1541 
1542     doc()->indent(KTextEditor::Range(line1, 0, line2, doc()->lineLength(line2)), -getCount());
1543 
1544     if (downwards) {
1545         updateCursor(KTextEditor::Cursor(m_commandRange.startLine, m_commandRange.startColumn));
1546     } else {
1547         updateCursor(KTextEditor::Cursor(m_commandRange.endLine, m_commandRange.endColumn));
1548     }
1549     return true;
1550 }
1551 
commandScrollPageDown()1552 bool NormalViMode::commandScrollPageDown()
1553 {
1554     if (getCount() < m_scroll_count_limit) {
1555         for (int i = 0; i < getCount(); i++) {
1556             m_view->pageDown();
1557         }
1558     }
1559     return true;
1560 }
1561 
commandScrollPageUp()1562 bool NormalViMode::commandScrollPageUp()
1563 {
1564     if (getCount() < m_scroll_count_limit) {
1565         for (int i = 0; i < getCount(); i++) {
1566             m_view->pageUp();
1567         }
1568     }
1569     return true;
1570 }
1571 
commandScrollHalfPageUp()1572 bool NormalViMode::commandScrollHalfPageUp()
1573 {
1574     if (getCount() < m_scroll_count_limit) {
1575         for (int i = 0; i < getCount(); i++) {
1576             m_viewInternal->pageUp(false, true);
1577         }
1578     }
1579     return true;
1580 }
1581 
commandScrollHalfPageDown()1582 bool NormalViMode::commandScrollHalfPageDown()
1583 {
1584     if (getCount() < m_scroll_count_limit) {
1585         for (int i = 0; i < getCount(); i++) {
1586             m_viewInternal->pageDown(false, true);
1587         }
1588     }
1589     return true;
1590 }
1591 
commandCenterView(bool onFirst)1592 bool NormalViMode::commandCenterView(bool onFirst)
1593 {
1594     KTextEditor::Cursor c(m_view->cursorPosition());
1595     const int virtualCenterLine = m_viewInternal->startLine() + linesDisplayed() / 2;
1596     const int virtualCursorLine = m_view->textFolding().lineToVisibleLine(c.line());
1597 
1598     scrollViewLines(virtualCursorLine - virtualCenterLine);
1599     if (onFirst) {
1600         c.setColumn(getFirstNonBlank());
1601         updateCursor(c);
1602     }
1603     return true;
1604 }
1605 
commandCenterViewOnNonBlank()1606 bool NormalViMode::commandCenterViewOnNonBlank()
1607 {
1608     return commandCenterView(true);
1609 }
1610 
commandCenterViewOnCursor()1611 bool NormalViMode::commandCenterViewOnCursor()
1612 {
1613     return commandCenterView(false);
1614 }
1615 
commandTopView(bool onFirst)1616 bool NormalViMode::commandTopView(bool onFirst)
1617 {
1618     KTextEditor::Cursor c(m_view->cursorPosition());
1619     const int virtualCenterLine = m_viewInternal->startLine();
1620     const int virtualCursorLine = m_view->textFolding().lineToVisibleLine(c.line());
1621 
1622     scrollViewLines(virtualCursorLine - virtualCenterLine);
1623     if (onFirst) {
1624         c.setColumn(getFirstNonBlank());
1625         updateCursor(c);
1626     }
1627     return true;
1628 }
1629 
commandTopViewOnNonBlank()1630 bool NormalViMode::commandTopViewOnNonBlank()
1631 {
1632     return commandTopView(true);
1633 }
1634 
commandTopViewOnCursor()1635 bool NormalViMode::commandTopViewOnCursor()
1636 {
1637     return commandTopView(false);
1638 }
1639 
commandBottomView(bool onFirst)1640 bool NormalViMode::commandBottomView(bool onFirst)
1641 {
1642     KTextEditor::Cursor c(m_view->cursorPosition());
1643     const int virtualCenterLine = m_viewInternal->endLine();
1644     const int virtualCursorLine = m_view->textFolding().lineToVisibleLine(c.line());
1645 
1646     scrollViewLines(virtualCursorLine - virtualCenterLine);
1647     if (onFirst) {
1648         c.setColumn(getFirstNonBlank());
1649         updateCursor(c);
1650     }
1651     return true;
1652 }
1653 
commandBottomViewOnNonBlank()1654 bool NormalViMode::commandBottomViewOnNonBlank()
1655 {
1656     return commandBottomView(true);
1657 }
1658 
commandBottomViewOnCursor()1659 bool NormalViMode::commandBottomViewOnCursor()
1660 {
1661     return commandBottomView(false);
1662 }
1663 
commandAbort()1664 bool NormalViMode::commandAbort()
1665 {
1666     m_pendingResetIsDueToExit = true;
1667     reset();
1668     return true;
1669 }
1670 
commandPrintCharacterCode()1671 bool NormalViMode::commandPrintCharacterCode()
1672 {
1673     QChar ch = getCharUnderCursor();
1674 
1675     if (ch == QChar::Null) {
1676         message(QStringLiteral("NUL"));
1677     } else {
1678         int code = ch.unicode();
1679 
1680         QString dec = QString::number(code);
1681         QString hex = QString::number(code, 16);
1682         QString oct = QString::number(code, 8);
1683         if (oct.length() < 3) {
1684             oct.prepend(QLatin1Char('0'));
1685         }
1686         if (code > 0x80 && code < 0x1000) {
1687             hex.prepend((code < 0x100 ? QLatin1String("00") : QLatin1String("0")));
1688         }
1689         message(i18n("'%1' %2,  Hex %3,  Octal %4", ch, dec, hex, oct));
1690     }
1691 
1692     return true;
1693 }
1694 
commandRepeatLastChange()1695 bool NormalViMode::commandRepeatLastChange()
1696 {
1697     const int repeatCount = getCount();
1698     resetParser();
1699     if (repeatCount > 1) {
1700         m_oneTimeCountOverride = repeatCount;
1701     }
1702     doc()->editStart();
1703     m_viInputModeManager->repeatLastChange();
1704     doc()->editEnd();
1705 
1706     return true;
1707 }
1708 
commandAlignLine()1709 bool NormalViMode::commandAlignLine()
1710 {
1711     const int line = m_view->cursorPosition().line();
1712     KTextEditor::Range alignRange(KTextEditor::Cursor(line, 0), KTextEditor::Cursor(line, 0));
1713 
1714     doc()->align(m_view, alignRange);
1715 
1716     return true;
1717 }
1718 
commandAlignLines()1719 bool NormalViMode::commandAlignLines()
1720 {
1721     m_commandRange.normalize();
1722 
1723     KTextEditor::Cursor start(m_commandRange.startLine, 0);
1724     KTextEditor::Cursor end(m_commandRange.endLine, 0);
1725 
1726     doc()->align(m_view, KTextEditor::Range(start, end));
1727 
1728     return true;
1729 }
1730 
commandAddToNumber()1731 bool NormalViMode::commandAddToNumber()
1732 {
1733     addToNumberUnderCursor(getCount());
1734 
1735     return true;
1736 }
1737 
commandSubtractFromNumber()1738 bool NormalViMode::commandSubtractFromNumber()
1739 {
1740     addToNumberUnderCursor(-getCount());
1741 
1742     return true;
1743 }
1744 
commandPrependToBlock()1745 bool NormalViMode::commandPrependToBlock()
1746 {
1747     KTextEditor::Cursor c(m_view->cursorPosition());
1748 
1749     // move cursor to top left corner of selection
1750     m_commandRange.normalize();
1751     c.setColumn(m_commandRange.startColumn);
1752     c.setLine(m_commandRange.startLine);
1753     updateCursor(c);
1754 
1755     m_stickyColumn = -1;
1756     m_viInputModeManager->getViInsertMode()->setBlockPrependMode(m_commandRange);
1757     return startInsertMode();
1758 }
1759 
commandAppendToBlock()1760 bool NormalViMode::commandAppendToBlock()
1761 {
1762     KTextEditor::Cursor c(m_view->cursorPosition());
1763 
1764     m_commandRange.normalize();
1765     if (m_stickyColumn == (unsigned int)KateVi::EOL) { // append to EOL
1766         // move cursor to end of first line
1767         c.setLine(m_commandRange.startLine);
1768         c.setColumn(doc()->lineLength(c.line()));
1769         updateCursor(c);
1770         m_viInputModeManager->getViInsertMode()->setBlockAppendMode(m_commandRange, AppendEOL);
1771     } else {
1772         m_viInputModeManager->getViInsertMode()->setBlockAppendMode(m_commandRange, Append);
1773         // move cursor to top right corner of selection
1774         c.setColumn(m_commandRange.endColumn + 1);
1775         c.setLine(m_commandRange.startLine);
1776         updateCursor(c);
1777     }
1778 
1779     m_stickyColumn = -1;
1780 
1781     return startInsertMode();
1782 }
1783 
commandGoToNextJump()1784 bool NormalViMode::commandGoToNextJump()
1785 {
1786     KTextEditor::Cursor c = getNextJump(m_view->cursorPosition());
1787     updateCursor(c);
1788 
1789     return true;
1790 }
1791 
commandGoToPrevJump()1792 bool NormalViMode::commandGoToPrevJump()
1793 {
1794     KTextEditor::Cursor c = getPrevJump(m_view->cursorPosition());
1795     updateCursor(c);
1796 
1797     return true;
1798 }
1799 
commandSwitchToLeftView()1800 bool NormalViMode::commandSwitchToLeftView()
1801 {
1802     switchView(Left);
1803     return true;
1804 }
1805 
commandSwitchToDownView()1806 bool NormalViMode::commandSwitchToDownView()
1807 {
1808     switchView(Down);
1809     return true;
1810 }
1811 
commandSwitchToUpView()1812 bool NormalViMode::commandSwitchToUpView()
1813 {
1814     switchView(Up);
1815     return true;
1816 }
1817 
commandSwitchToRightView()1818 bool NormalViMode::commandSwitchToRightView()
1819 {
1820     switchView(Right);
1821     return true;
1822 }
1823 
commandSwitchToNextView()1824 bool NormalViMode::commandSwitchToNextView()
1825 {
1826     switchView(Next);
1827     return true;
1828 }
1829 
commandSplitHoriz()1830 bool NormalViMode::commandSplitHoriz()
1831 {
1832     return executeKateCommand(QStringLiteral("split"));
1833 }
1834 
commandSplitVert()1835 bool NormalViMode::commandSplitVert()
1836 {
1837     return executeKateCommand(QStringLiteral("vsplit"));
1838 }
1839 
commandCloseView()1840 bool NormalViMode::commandCloseView()
1841 {
1842     return executeKateCommand(QStringLiteral("close"));
1843 }
1844 
commandSwitchToNextTab()1845 bool NormalViMode::commandSwitchToNextTab()
1846 {
1847     QString command = QStringLiteral("bn");
1848 
1849     if (m_iscounted) {
1850         command = command + QLatin1Char(' ') + QString::number(getCount());
1851     }
1852 
1853     return executeKateCommand(command);
1854 }
1855 
commandSwitchToPrevTab()1856 bool NormalViMode::commandSwitchToPrevTab()
1857 {
1858     QString command = QStringLiteral("bp");
1859 
1860     if (m_iscounted) {
1861         command = command + QLatin1Char(' ') + QString::number(getCount());
1862     }
1863 
1864     return executeKateCommand(command);
1865 }
1866 
commandFormatLine()1867 bool NormalViMode::commandFormatLine()
1868 {
1869     KTextEditor::Cursor c(m_view->cursorPosition());
1870 
1871     reformatLines(c.line(), c.line() + getCount() - 1);
1872 
1873     return true;
1874 }
1875 
commandFormatLines()1876 bool NormalViMode::commandFormatLines()
1877 {
1878     reformatLines(m_commandRange.startLine, m_commandRange.endLine);
1879     return true;
1880 }
1881 
commandCollapseToplevelNodes()1882 bool NormalViMode::commandCollapseToplevelNodes()
1883 {
1884 #if 0
1885     //FIXME FOLDING
1886     doc()->foldingTree()->collapseToplevelNodes();
1887 #endif
1888     return true;
1889 }
1890 
commandStartRecordingMacro()1891 bool NormalViMode::commandStartRecordingMacro()
1892 {
1893     const QChar reg = m_keys[m_keys.size() - 1];
1894     m_viInputModeManager->macroRecorder()->start(reg);
1895     return true;
1896 }
1897 
commandReplayMacro()1898 bool NormalViMode::commandReplayMacro()
1899 {
1900     // "@<registername>" will have been added to the log; it needs to be cleared
1901     // *before* we replay the macro keypresses, else it can cause an infinite loop
1902     // if the macro contains a "."
1903     m_viInputModeManager->clearCurrentChangeLog();
1904     const QChar reg = m_keys[m_keys.size() - 1];
1905     const unsigned int count = getCount();
1906     resetParser();
1907     doc()->editBegin();
1908     for (unsigned int i = 0; i < count; i++) {
1909         m_viInputModeManager->macroRecorder()->replay(reg);
1910     }
1911     doc()->editEnd();
1912     return true;
1913 }
1914 
commandCloseNocheck()1915 bool NormalViMode::commandCloseNocheck()
1916 {
1917     return executeKateCommand(QStringLiteral("q!"));
1918 }
1919 
commandCloseWrite()1920 bool NormalViMode::commandCloseWrite()
1921 {
1922     return executeKateCommand(QStringLiteral("wq"));
1923 }
1924 
commandCollapseLocal()1925 bool NormalViMode::commandCollapseLocal()
1926 {
1927 #if 0
1928     //FIXME FOLDING
1929     KTextEditor::Cursor c(m_view->cursorPosition());
1930     doc()->foldingTree()->collapseOne(c.line(), c.column());
1931 #endif
1932     return true;
1933 }
1934 
commandExpandAll()1935 bool NormalViMode::commandExpandAll()
1936 {
1937 #if 0
1938     //FIXME FOLDING
1939     doc()->foldingTree()->expandAll();
1940 #endif
1941     return true;
1942 }
1943 
commandExpandLocal()1944 bool NormalViMode::commandExpandLocal()
1945 {
1946 #if 0
1947     //FIXME FOLDING
1948     KTextEditor::Cursor c(m_view->cursorPosition());
1949     doc()->foldingTree()->expandOne(c.line() + 1, c.column());
1950 #endif
1951     return true;
1952 }
1953 
commandToggleRegionVisibility()1954 bool NormalViMode::commandToggleRegionVisibility()
1955 {
1956 #if 0
1957     //FIXME FOLDING
1958     KTextEditor::Cursor c(m_view->cursorPosition());
1959     doc()->foldingTree()->toggleRegionVisibility(c.line());
1960 #endif
1961     return true;
1962 }
1963 
1964 ////////////////////////////////////////////////////////////////////////////////
1965 // MOTIONS
1966 ////////////////////////////////////////////////////////////////////////////////
1967 
motionDown()1968 Range NormalViMode::motionDown()
1969 {
1970     return goLineDown();
1971 }
1972 
motionUp()1973 Range NormalViMode::motionUp()
1974 {
1975     return goLineUp();
1976 }
1977 
motionLeft()1978 Range NormalViMode::motionLeft()
1979 {
1980     KTextEditor::Cursor cursor(m_view->cursorPosition());
1981     m_stickyColumn = -1;
1982     Range r(cursor, ExclusiveMotion);
1983     r.endColumn -= getCount();
1984 
1985     if (r.endColumn < 0) {
1986         r.endColumn = 0;
1987     }
1988 
1989     return r;
1990 }
1991 
motionRight()1992 Range NormalViMode::motionRight()
1993 {
1994     KTextEditor::Cursor cursor(m_view->cursorPosition());
1995     m_stickyColumn = -1;
1996     Range r(cursor, ExclusiveMotion);
1997     r.endColumn += getCount();
1998 
1999     // make sure end position isn't > line length
2000     if (r.endColumn > doc()->lineLength(r.endLine)) {
2001         r.endColumn = doc()->lineLength(r.endLine);
2002     }
2003 
2004     return r;
2005 }
2006 
motionPageDown()2007 Range NormalViMode::motionPageDown()
2008 {
2009     KTextEditor::Cursor c(m_view->cursorPosition());
2010     Range r(c, InclusiveMotion);
2011     r.endLine += linesDisplayed();
2012 
2013     if (r.endLine >= doc()->lines()) {
2014         r.endLine = doc()->lines() - 1;
2015     }
2016     return r;
2017 }
2018 
motionPageUp()2019 Range NormalViMode::motionPageUp()
2020 {
2021     KTextEditor::Cursor c(m_view->cursorPosition());
2022     Range r(c, InclusiveMotion);
2023     r.endLine -= linesDisplayed();
2024 
2025     if (r.endLine < 0) {
2026         r.endLine = 0;
2027     }
2028     return r;
2029 }
2030 
motionHalfPageDown()2031 Range NormalViMode::motionHalfPageDown()
2032 {
2033     if (commandScrollHalfPageDown()) {
2034         KTextEditor::Cursor c = m_view->cursorPosition();
2035         m_commandRange.endLine = c.line();
2036         m_commandRange.endColumn = c.column();
2037         return m_commandRange;
2038     }
2039     return Range::invalid();
2040 }
2041 
motionHalfPageUp()2042 Range NormalViMode::motionHalfPageUp()
2043 {
2044     if (commandScrollHalfPageUp()) {
2045         KTextEditor::Cursor c = m_view->cursorPosition();
2046         m_commandRange.endLine = c.line();
2047         m_commandRange.endColumn = c.column();
2048         return m_commandRange;
2049     }
2050     return Range::invalid();
2051 }
2052 
motionDownToFirstNonBlank()2053 Range NormalViMode::motionDownToFirstNonBlank()
2054 {
2055     Range r = goLineDown();
2056     r.endColumn = getFirstNonBlank(r.endLine);
2057     return r;
2058 }
2059 
motionUpToFirstNonBlank()2060 Range NormalViMode::motionUpToFirstNonBlank()
2061 {
2062     Range r = goLineUp();
2063     r.endColumn = getFirstNonBlank(r.endLine);
2064     return r;
2065 }
2066 
motionWordForward()2067 Range NormalViMode::motionWordForward()
2068 {
2069     KTextEditor::Cursor c(m_view->cursorPosition());
2070     Range r(c, ExclusiveMotion);
2071 
2072     m_stickyColumn = -1;
2073 
2074     // Special case: If we're already on the very last character in the document, the motion should be
2075     // inclusive so the last character gets included
2076     if (c.line() == doc()->lines() - 1 && c.column() == doc()->lineLength(c.line()) - 1) {
2077         r.motionType = InclusiveMotion;
2078     } else {
2079         for (int i = 0; i < getCount(); i++) {
2080             c = findNextWordStart(c.line(), c.column());
2081 
2082             // stop when at the last char in the document
2083             if (!c.isValid()) {
2084                 c = doc()->documentEnd();
2085                 // if we still haven't "used up the count", make the motion inclusive, so that the last char
2086                 // is included
2087                 if (i < getCount()) {
2088                     r.motionType = InclusiveMotion;
2089                 }
2090                 break;
2091             }
2092         }
2093     }
2094 
2095     r.endColumn = c.column();
2096     r.endLine = c.line();
2097 
2098     return r;
2099 }
2100 
motionWordBackward()2101 Range NormalViMode::motionWordBackward()
2102 {
2103     KTextEditor::Cursor c(m_view->cursorPosition());
2104     Range r(c, ExclusiveMotion);
2105 
2106     m_stickyColumn = -1;
2107 
2108     for (int i = 0; i < getCount(); i++) {
2109         c = findPrevWordStart(c.line(), c.column());
2110 
2111         if (!c.isValid()) {
2112             c = KTextEditor::Cursor(0, 0);
2113             break;
2114         }
2115     }
2116 
2117     r.endColumn = c.column();
2118     r.endLine = c.line();
2119 
2120     return r;
2121 }
2122 
motionWORDForward()2123 Range NormalViMode::motionWORDForward()
2124 {
2125     KTextEditor::Cursor c(m_view->cursorPosition());
2126     Range r(c, ExclusiveMotion);
2127 
2128     m_stickyColumn = -1;
2129 
2130     for (int i = 0; i < getCount(); i++) {
2131         c = findNextWORDStart(c.line(), c.column());
2132 
2133         // stop when at the last char in the document
2134         if (c.line() == doc()->lines() - 1 && c.column() == doc()->lineLength(c.line()) - 1) {
2135             break;
2136         }
2137     }
2138 
2139     r.endColumn = c.column();
2140     r.endLine = c.line();
2141 
2142     return r;
2143 }
2144 
motionWORDBackward()2145 Range NormalViMode::motionWORDBackward()
2146 {
2147     KTextEditor::Cursor c(m_view->cursorPosition());
2148     Range r(c, ExclusiveMotion);
2149 
2150     m_stickyColumn = -1;
2151 
2152     for (int i = 0; i < getCount(); i++) {
2153         c = findPrevWORDStart(c.line(), c.column());
2154 
2155         if (!c.isValid()) {
2156             c = KTextEditor::Cursor(0, 0);
2157         }
2158     }
2159 
2160     r.endColumn = c.column();
2161     r.endLine = c.line();
2162 
2163     return r;
2164 }
2165 
motionToEndOfWord()2166 Range NormalViMode::motionToEndOfWord()
2167 {
2168     KTextEditor::Cursor c(m_view->cursorPosition());
2169     Range r(c, InclusiveMotion);
2170 
2171     m_stickyColumn = -1;
2172 
2173     for (int i = 0; i < getCount(); i++) {
2174         c = findWordEnd(c.line(), c.column());
2175     }
2176 
2177     if (!c.isValid()) {
2178         c = doc()->documentEnd();
2179     }
2180 
2181     r.endColumn = c.column();
2182     r.endLine = c.line();
2183 
2184     return r;
2185 }
2186 
motionToEndOfWORD()2187 Range NormalViMode::motionToEndOfWORD()
2188 {
2189     KTextEditor::Cursor c(m_view->cursorPosition());
2190     Range r(c, InclusiveMotion);
2191 
2192     m_stickyColumn = -1;
2193 
2194     for (int i = 0; i < getCount(); i++) {
2195         c = findWORDEnd(c.line(), c.column());
2196     }
2197 
2198     if (!c.isValid()) {
2199         c = doc()->documentEnd();
2200     }
2201 
2202     r.endColumn = c.column();
2203     r.endLine = c.line();
2204 
2205     return r;
2206 }
2207 
motionToEndOfPrevWord()2208 Range NormalViMode::motionToEndOfPrevWord()
2209 {
2210     KTextEditor::Cursor c(m_view->cursorPosition());
2211     Range r(c, InclusiveMotion);
2212 
2213     m_stickyColumn = -1;
2214 
2215     for (int i = 0; i < getCount(); i++) {
2216         c = findPrevWordEnd(c.line(), c.column());
2217 
2218         if (c.isValid()) {
2219             r.endColumn = c.column();
2220             r.endLine = c.line();
2221         } else {
2222             r.endColumn = 0;
2223             r.endLine = 0;
2224             break;
2225         }
2226     }
2227 
2228     return r;
2229 }
2230 
motionToEndOfPrevWORD()2231 Range NormalViMode::motionToEndOfPrevWORD()
2232 {
2233     KTextEditor::Cursor c(m_view->cursorPosition());
2234     Range r(c, InclusiveMotion);
2235 
2236     m_stickyColumn = -1;
2237 
2238     for (int i = 0; i < getCount(); i++) {
2239         c = findPrevWORDEnd(c.line(), c.column());
2240 
2241         if (c.isValid()) {
2242             r.endColumn = c.column();
2243             r.endLine = c.line();
2244         } else {
2245             r.endColumn = 0;
2246             r.endLine = 0;
2247             break;
2248         }
2249     }
2250 
2251     return r;
2252 }
2253 
motionToEOL()2254 Range NormalViMode::motionToEOL()
2255 {
2256     KTextEditor::Cursor c(m_view->cursorPosition());
2257 
2258     // set sticky column to a ridiculously high value so that the cursor will stick to EOL,
2259     // but only if it's a regular motion
2260     if (m_keys.size() == 1) {
2261         m_stickyColumn = KateVi::EOL;
2262     }
2263 
2264     unsigned int line = c.line() + (getCount() - 1);
2265     Range r(line, doc()->lineLength(line) - 1, InclusiveMotion);
2266 
2267     return r;
2268 }
2269 
motionToColumn0()2270 Range NormalViMode::motionToColumn0()
2271 {
2272     m_stickyColumn = -1;
2273     KTextEditor::Cursor cursor(m_view->cursorPosition());
2274     Range r(cursor.line(), 0, ExclusiveMotion);
2275 
2276     return r;
2277 }
2278 
motionToFirstCharacterOfLine()2279 Range NormalViMode::motionToFirstCharacterOfLine()
2280 {
2281     m_stickyColumn = -1;
2282 
2283     KTextEditor::Cursor cursor(m_view->cursorPosition());
2284     int c = getFirstNonBlank();
2285 
2286     Range r(cursor.line(), c, ExclusiveMotion);
2287 
2288     return r;
2289 }
2290 
motionFindChar()2291 Range NormalViMode::motionFindChar()
2292 {
2293     m_lastTFcommand = m_keys;
2294     KTextEditor::Cursor cursor(m_view->cursorPosition());
2295     QString line = getLine();
2296 
2297     m_stickyColumn = -1;
2298 
2299     int matchColumn = cursor.column();
2300 
2301     for (int i = 0; i < getCount(); i++) {
2302         matchColumn = line.indexOf(QStringView(m_keys).right(1), matchColumn + 1);
2303         if (matchColumn == -1) {
2304             break;
2305         }
2306     }
2307 
2308     Range r;
2309 
2310     if (matchColumn != -1) {
2311         r.endColumn = matchColumn;
2312         r.endLine = cursor.line();
2313     } else {
2314         return Range::invalid();
2315     }
2316 
2317     return r;
2318 }
2319 
motionFindCharBackward()2320 Range NormalViMode::motionFindCharBackward()
2321 {
2322     m_lastTFcommand = m_keys;
2323     KTextEditor::Cursor cursor(m_view->cursorPosition());
2324     QString line = getLine();
2325 
2326     m_stickyColumn = -1;
2327 
2328     int matchColumn = -1;
2329 
2330     int hits = 0;
2331     int i = cursor.column() - 1;
2332 
2333     while (hits != getCount() && i >= 0) {
2334         if (line.at(i) == m_keys.at(m_keys.size() - 1)) {
2335             hits++;
2336         }
2337 
2338         if (hits == getCount()) {
2339             matchColumn = i;
2340         }
2341 
2342         i--;
2343     }
2344 
2345     Range r(cursor, ExclusiveMotion);
2346 
2347     if (matchColumn != -1) {
2348         r.endColumn = matchColumn;
2349         r.endLine = cursor.line();
2350     } else {
2351         return Range::invalid();
2352     }
2353 
2354     return r;
2355 }
2356 
motionToChar()2357 Range NormalViMode::motionToChar()
2358 {
2359     m_lastTFcommand = m_keys;
2360     KTextEditor::Cursor cursor(m_view->cursorPosition());
2361     QString line = getLine();
2362 
2363     m_stickyColumn = -1;
2364     Range r;
2365     r.endColumn = -1;
2366     r.endLine = -1;
2367 
2368     int matchColumn = cursor.column() + (m_isRepeatedTFcommand ? 2 : 1);
2369 
2370     for (int i = 0; i < getCount(); i++) {
2371         const int lastColumn = matchColumn;
2372         matchColumn = line.indexOf(m_keys.right(1), matchColumn + ((i > 0) ? 1 : 0));
2373         if (matchColumn == -1) {
2374             if (m_isRepeatedTFcommand) {
2375                 matchColumn = lastColumn;
2376             } else {
2377                 return Range::invalid();
2378             }
2379             break;
2380         }
2381     }
2382 
2383     r.endColumn = matchColumn - 1;
2384     r.endLine = cursor.line();
2385 
2386     m_isRepeatedTFcommand = false;
2387     return r;
2388 }
2389 
motionToCharBackward()2390 Range NormalViMode::motionToCharBackward()
2391 {
2392     m_lastTFcommand = m_keys;
2393     KTextEditor::Cursor cursor(m_view->cursorPosition());
2394     QString line = getLine();
2395 
2396     const int originalColumn = cursor.column();
2397     m_stickyColumn = -1;
2398 
2399     int matchColumn = originalColumn - 1;
2400 
2401     int hits = 0;
2402     int i = cursor.column() - (m_isRepeatedTFcommand ? 2 : 1);
2403 
2404     Range r(cursor, ExclusiveMotion);
2405 
2406     while (hits != getCount() && i >= 0) {
2407         if (line.at(i) == m_keys.at(m_keys.size() - 1)) {
2408             hits++;
2409         }
2410 
2411         if (hits == getCount()) {
2412             matchColumn = i;
2413         }
2414 
2415         i--;
2416     }
2417 
2418     if (hits == getCount()) {
2419         r.endColumn = matchColumn + 1;
2420         r.endLine = cursor.line();
2421     } else {
2422         r.valid = false;
2423     }
2424 
2425     m_isRepeatedTFcommand = false;
2426 
2427     return r;
2428 }
2429 
motionRepeatlastTF()2430 Range NormalViMode::motionRepeatlastTF()
2431 {
2432     if (!m_lastTFcommand.isEmpty()) {
2433         m_isRepeatedTFcommand = true;
2434         m_keys = m_lastTFcommand;
2435         if (m_keys.at(0) == QLatin1Char('f')) {
2436             return motionFindChar();
2437         } else if (m_keys.at(0) == QLatin1Char('F')) {
2438             return motionFindCharBackward();
2439         } else if (m_keys.at(0) == QLatin1Char('t')) {
2440             return motionToChar();
2441         } else if (m_keys.at(0) == QLatin1Char('T')) {
2442             return motionToCharBackward();
2443         }
2444     }
2445 
2446     // there was no previous t/f command
2447     return Range::invalid();
2448 }
2449 
motionRepeatlastTFBackward()2450 Range NormalViMode::motionRepeatlastTFBackward()
2451 {
2452     if (!m_lastTFcommand.isEmpty()) {
2453         m_isRepeatedTFcommand = true;
2454         m_keys = m_lastTFcommand;
2455         if (m_keys.at(0) == QLatin1Char('f')) {
2456             return motionFindCharBackward();
2457         } else if (m_keys.at(0) == QLatin1Char('F')) {
2458             return motionFindChar();
2459         } else if (m_keys.at(0) == QLatin1Char('t')) {
2460             return motionToCharBackward();
2461         } else if (m_keys.at(0) == QLatin1Char('T')) {
2462             return motionToChar();
2463         }
2464     }
2465 
2466     // there was no previous t/f command
2467     return Range::invalid();
2468 }
2469 
motionToLineFirst()2470 Range NormalViMode::motionToLineFirst()
2471 {
2472     Range r(getCount() - 1, 0, InclusiveMotion);
2473     m_stickyColumn = -1;
2474 
2475     if (r.endLine > doc()->lines() - 1) {
2476         r.endLine = doc()->lines() - 1;
2477     }
2478     r.jump = true;
2479 
2480     return r;
2481 }
2482 
motionToLineLast()2483 Range NormalViMode::motionToLineLast()
2484 {
2485     Range r(doc()->lines() - 1, 0, InclusiveMotion);
2486     m_stickyColumn = -1;
2487 
2488     // don't use getCount() here, no count and a count of 1 is different here...
2489     if (m_count != 0) {
2490         r.endLine = m_count - 1;
2491     }
2492 
2493     if (r.endLine > doc()->lines() - 1) {
2494         r.endLine = doc()->lines() - 1;
2495     }
2496     r.jump = true;
2497 
2498     return r;
2499 }
2500 
motionToScreenColumn()2501 Range NormalViMode::motionToScreenColumn()
2502 {
2503     m_stickyColumn = -1;
2504 
2505     KTextEditor::Cursor c(m_view->cursorPosition());
2506 
2507     int column = getCount() - 1;
2508 
2509     if (doc()->lineLength(c.line()) - 1 < (int)getCount() - 1) {
2510         column = doc()->lineLength(c.line()) - 1;
2511     }
2512 
2513     return Range(c.line(), column, ExclusiveMotion);
2514 }
2515 
motionToMark()2516 Range NormalViMode::motionToMark()
2517 {
2518     Range r;
2519 
2520     m_stickyColumn = -1;
2521 
2522     QChar reg = m_keys.at(m_keys.size() - 1);
2523 
2524     KTextEditor::Cursor c = m_viInputModeManager->marks()->getMarkPosition(reg);
2525     if (c.isValid()) {
2526         r.endLine = c.line();
2527         r.endColumn = c.column();
2528     } else {
2529         error(i18n("Mark not set: %1", m_keys.right(1)));
2530         r.valid = false;
2531     }
2532 
2533     r.jump = true;
2534 
2535     return r;
2536 }
2537 
motionToMarkLine()2538 Range NormalViMode::motionToMarkLine()
2539 {
2540     Range r = motionToMark();
2541     r.endColumn = getFirstNonBlank(r.endLine);
2542     r.jump = true;
2543     m_stickyColumn = -1;
2544     return r;
2545 }
2546 
motionToMatchingItem()2547 Range NormalViMode::motionToMatchingItem()
2548 {
2549     Range r;
2550     int lines = doc()->lines();
2551 
2552     // If counted, then it's not a motion to matching item anymore,
2553     // but a motion to the N'th percentage of the document
2554     if (isCounted()) {
2555         int count = getCount();
2556         if (count > 100) {
2557             return r;
2558         }
2559         r.endLine = qRound(lines * count / 100.0) - 1;
2560         r.endColumn = 0;
2561         return r;
2562     }
2563 
2564     KTextEditor::Cursor c(m_view->cursorPosition());
2565 
2566     QString l = getLine();
2567     int n1 = l.indexOf(m_matchItemRegex, c.column());
2568 
2569     m_stickyColumn = -1;
2570 
2571     if (n1 < 0) {
2572         return Range::invalid();
2573     }
2574 
2575     const auto bracketChar = l.at(n1);
2576     // use Kate's built-in matching bracket finder for brackets
2577     if (bracketChar == QLatin1Char('(') || bracketChar == QLatin1Char(')') || bracketChar == QLatin1Char('{') || bracketChar == QLatin1Char('}')
2578         || bracketChar == QLatin1Char('[') || bracketChar == QLatin1Char(']')) {
2579         // findMatchingBracket requires us to move the cursor to the
2580         // first bracket, but we don't want the cursor to really move
2581         // in case this is e.g. a yank, so restore it to its original
2582         // position afterwards.
2583         c.setColumn(n1);
2584         const KTextEditor::Cursor oldCursorPos = m_view->cursorPosition();
2585         updateCursor(c);
2586 
2587         // find the matching one
2588         c = m_viewInternal->findMatchingBracket();
2589         if (c > m_view->cursorPosition()) {
2590             c.setColumn(c.column() - 1);
2591         }
2592         m_view->setCursorPosition(oldCursorPos);
2593     } else {
2594         // text item we want to find a matching item for
2595         static const QRegularExpression boundaryRegex(QStringLiteral("\\b|\\s|$"));
2596         const int n2 = l.indexOf(boundaryRegex, n1);
2597         QString item = l.mid(n1, n2 - n1);
2598         QString matchingItem = m_matchingItems[item];
2599 
2600         int toFind = 1;
2601         int line = c.line();
2602         int column = n2 - item.length();
2603         bool reverse = false;
2604 
2605         if (matchingItem.startsWith(QLatin1Char('-'))) {
2606             matchingItem.remove(0, 1); // remove the '-'
2607             reverse = true;
2608         }
2609 
2610         // make sure we don't hit the text item we started the search from
2611         if (column == 0 && reverse) {
2612             column -= item.length();
2613         }
2614 
2615         int itemIdx;
2616         int matchItemIdx;
2617 
2618         while (toFind > 0) {
2619             if (reverse) {
2620                 itemIdx = l.lastIndexOf(item, column - 1);
2621                 matchItemIdx = l.lastIndexOf(matchingItem, column - 1);
2622 
2623                 if (itemIdx != -1 && (matchItemIdx == -1 || itemIdx > matchItemIdx)) {
2624                     ++toFind;
2625                 }
2626             } else {
2627                 itemIdx = l.indexOf(item, column);
2628                 matchItemIdx = l.indexOf(matchingItem, column);
2629 
2630                 if (itemIdx != -1 && (matchItemIdx == -1 || itemIdx < matchItemIdx)) {
2631                     ++toFind;
2632                 }
2633             }
2634 
2635             if (matchItemIdx != -1 || itemIdx != -1) {
2636                 if (!reverse) {
2637                     column = qMin((unsigned int)itemIdx, (unsigned int)matchItemIdx);
2638                 } else {
2639                     column = qMax(itemIdx, matchItemIdx);
2640                 }
2641             }
2642 
2643             if (matchItemIdx != -1) { // match on current line
2644                 if (matchItemIdx == column) {
2645                     --toFind;
2646                     c.setLine(line);
2647                     c.setColumn(column);
2648                 }
2649             } else { // no match, advance one line if possible
2650                 (reverse) ? --line : ++line;
2651                 column = 0;
2652 
2653                 if ((!reverse && line >= lines) || (reverse && line < 0)) {
2654                     r.valid = false;
2655                     break;
2656                 } else {
2657                     l = getLine(line);
2658                 }
2659             }
2660         }
2661     }
2662 
2663     r.endLine = c.line();
2664     r.endColumn = c.column();
2665     r.jump = true;
2666 
2667     return r;
2668 }
2669 
motionToNextBraceBlockStart()2670 Range NormalViMode::motionToNextBraceBlockStart()
2671 {
2672     Range r;
2673 
2674     m_stickyColumn = -1;
2675 
2676     int line = findLineStartingWitchChar(QLatin1Char('{'), getCount());
2677 
2678     if (line == -1) {
2679         return Range::invalid();
2680     }
2681 
2682     r.endLine = line;
2683     r.endColumn = 0;
2684     r.jump = true;
2685 
2686     if (motionWillBeUsedWithCommand()) {
2687         // Delete from cursor (inclusive) to the '{' (exclusive).
2688         // If we are on the first column, then delete the entire current line.
2689         r.motionType = ExclusiveMotion;
2690         if (m_view->cursorPosition().column() != 0) {
2691             r.endLine--;
2692             r.endColumn = doc()->lineLength(r.endLine);
2693         }
2694     }
2695 
2696     return r;
2697 }
2698 
motionToPreviousBraceBlockStart()2699 Range NormalViMode::motionToPreviousBraceBlockStart()
2700 {
2701     Range r;
2702 
2703     m_stickyColumn = -1;
2704 
2705     int line = findLineStartingWitchChar(QLatin1Char('{'), getCount(), false);
2706 
2707     if (line == -1) {
2708         return Range::invalid();
2709     }
2710 
2711     r.endLine = line;
2712     r.endColumn = 0;
2713     r.jump = true;
2714 
2715     if (motionWillBeUsedWithCommand()) {
2716         // With a command, do not include the { or the cursor position.
2717         r.motionType = ExclusiveMotion;
2718     }
2719 
2720     return r;
2721 }
2722 
motionToNextBraceBlockEnd()2723 Range NormalViMode::motionToNextBraceBlockEnd()
2724 {
2725     Range r;
2726 
2727     m_stickyColumn = -1;
2728 
2729     int line = findLineStartingWitchChar(QLatin1Char('}'), getCount());
2730 
2731     if (line == -1) {
2732         return Range::invalid();
2733     }
2734 
2735     r.endLine = line;
2736     r.endColumn = 0;
2737     r.jump = true;
2738 
2739     if (motionWillBeUsedWithCommand()) {
2740         // Delete from cursor (inclusive) to the '}' (exclusive).
2741         // If we are on the first column, then delete the entire current line.
2742         r.motionType = ExclusiveMotion;
2743         if (m_view->cursorPosition().column() != 0) {
2744             r.endLine--;
2745             r.endColumn = doc()->lineLength(r.endLine);
2746         }
2747     }
2748 
2749     return r;
2750 }
2751 
motionToPreviousBraceBlockEnd()2752 Range NormalViMode::motionToPreviousBraceBlockEnd()
2753 {
2754     Range r;
2755 
2756     m_stickyColumn = -1;
2757 
2758     int line = findLineStartingWitchChar(QLatin1Char('}'), getCount(), false);
2759 
2760     if (line == -1) {
2761         return Range::invalid();
2762     }
2763 
2764     r.endLine = line;
2765     r.endColumn = 0;
2766     r.jump = true;
2767 
2768     if (motionWillBeUsedWithCommand()) {
2769         r.motionType = ExclusiveMotion;
2770     }
2771 
2772     return r;
2773 }
2774 
motionToNextOccurrence()2775 Range NormalViMode::motionToNextOccurrence()
2776 {
2777     const QString word = getWordUnderCursor();
2778     Searcher *searcher = m_viInputModeManager->searcher();
2779     const Range match = searcher->findWordForMotion(word, false, getWordRangeUnderCursor().start(), getCount());
2780     if (searcher->lastSearchWrapped()) {
2781         m_view->showSearchWrappedHint(/*isReverseSearch*/ false);
2782     }
2783 
2784     return Range(match.startLine, match.startColumn, ExclusiveMotion);
2785 }
2786 
motionToPrevOccurrence()2787 Range NormalViMode::motionToPrevOccurrence()
2788 {
2789     const QString word = getWordUnderCursor();
2790     Searcher *searcher = m_viInputModeManager->searcher();
2791     const Range match = searcher->findWordForMotion(word, true, getWordRangeUnderCursor().start(), getCount());
2792     if (searcher->lastSearchWrapped()) {
2793         m_view->showSearchWrappedHint(/*isReverseSearch*/ true);
2794     }
2795 
2796     return Range(match.startLine, match.startColumn, ExclusiveMotion);
2797 }
2798 
motionToFirstLineOfWindow()2799 Range NormalViMode::motionToFirstLineOfWindow()
2800 {
2801     int lines_to_go;
2802     if (linesDisplayed() <= (unsigned int)m_viewInternal->endLine()) {
2803         lines_to_go = m_viewInternal->endLine() - linesDisplayed() - m_view->cursorPosition().line() + 1;
2804     } else {
2805         lines_to_go = -m_view->cursorPosition().line();
2806     }
2807 
2808     Range r = goLineUpDown(lines_to_go);
2809     r.endColumn = getFirstNonBlank(r.endLine);
2810     return r;
2811 }
2812 
motionToMiddleLineOfWindow()2813 Range NormalViMode::motionToMiddleLineOfWindow()
2814 {
2815     int lines_to_go;
2816     if (linesDisplayed() <= (unsigned int)m_viewInternal->endLine()) {
2817         lines_to_go = m_viewInternal->endLine() - linesDisplayed() / 2 - m_view->cursorPosition().line();
2818     } else {
2819         lines_to_go = m_viewInternal->endLine() / 2 - m_view->cursorPosition().line();
2820     }
2821 
2822     Range r = goLineUpDown(lines_to_go);
2823     r.endColumn = getFirstNonBlank(r.endLine);
2824     return r;
2825 }
2826 
motionToLastLineOfWindow()2827 Range NormalViMode::motionToLastLineOfWindow()
2828 {
2829     int lines_to_go;
2830     if (linesDisplayed() <= (unsigned int)m_viewInternal->endLine()) {
2831         lines_to_go = m_viewInternal->endLine() - m_view->cursorPosition().line();
2832     } else {
2833         lines_to_go = m_viewInternal->endLine() - m_view->cursorPosition().line();
2834     }
2835 
2836     Range r = goLineUpDown(lines_to_go);
2837     r.endColumn = getFirstNonBlank(r.endLine);
2838     return r;
2839 }
2840 
motionToNextVisualLine()2841 Range NormalViMode::motionToNextVisualLine()
2842 {
2843     return goVisualLineUpDown(getCount());
2844 }
2845 
motionToPrevVisualLine()2846 Range NormalViMode::motionToPrevVisualLine()
2847 {
2848     return goVisualLineUpDown(-getCount());
2849 }
2850 
motionToPreviousSentence()2851 Range NormalViMode::motionToPreviousSentence()
2852 {
2853     KTextEditor::Cursor c = findSentenceStart();
2854     int linenum = c.line();
2855     int column = c.column();
2856     const bool skipSpaces = doc()->line(linenum).isEmpty();
2857 
2858     if (skipSpaces) {
2859         linenum--;
2860         if (linenum >= 0) {
2861             column = doc()->line(linenum).size() - 1;
2862         }
2863     }
2864 
2865     for (int i = linenum; i >= 0; i--) {
2866         const QString &line = doc()->line(i);
2867 
2868         if (line.isEmpty() && !skipSpaces) {
2869             return Range(i, 0, InclusiveMotion);
2870         }
2871 
2872         if (column < 0 && !line.isEmpty()) {
2873             column = line.size() - 1;
2874         }
2875 
2876         for (int j = column; j >= 0; j--) {
2877             if (skipSpaces || QStringLiteral(".?!").indexOf(line.at(j)) != -1) {
2878                 c.setLine(i);
2879                 c.setColumn(j);
2880                 updateCursor(c);
2881                 c = findSentenceStart();
2882                 return Range(c, InclusiveMotion);
2883             }
2884         }
2885         column = line.size() - 1;
2886     }
2887     return Range(0, 0, InclusiveMotion);
2888 }
2889 
motionToNextSentence()2890 Range NormalViMode::motionToNextSentence()
2891 {
2892     KTextEditor::Cursor c = findSentenceEnd();
2893     int linenum = c.line();
2894     int column = c.column() + 1;
2895     const bool skipSpaces = doc()->line(linenum).isEmpty();
2896 
2897     for (int i = linenum; i < doc()->lines(); i++) {
2898         const QString &line = doc()->line(i);
2899 
2900         if (line.isEmpty() && !skipSpaces) {
2901             return Range(i, 0, InclusiveMotion);
2902         }
2903 
2904         for (int j = column; j < line.size(); j++) {
2905             if (!line.at(j).isSpace()) {
2906                 return Range(i, j, InclusiveMotion);
2907             }
2908         }
2909         column = 0;
2910     }
2911 
2912     c = doc()->documentEnd();
2913     return Range(c, InclusiveMotion);
2914 }
2915 
motionToBeforeParagraph()2916 Range NormalViMode::motionToBeforeParagraph()
2917 {
2918     KTextEditor::Cursor c(m_view->cursorPosition());
2919 
2920     int line = c.line();
2921 
2922     m_stickyColumn = -1;
2923 
2924     for (int i = 0; i < getCount(); i++) {
2925         // advance at least one line, but if there are consecutive blank lines
2926         // skip them all
2927         do {
2928             line--;
2929         } while (line >= 0 && getLine(line + 1).length() == 0);
2930         while (line > 0 && getLine(line).length() != 0) {
2931             line--;
2932         }
2933     }
2934 
2935     if (line < 0) {
2936         line = 0;
2937     }
2938 
2939     Range r(line, 0, InclusiveMotion);
2940 
2941     return r;
2942 }
2943 
motionToAfterParagraph()2944 Range NormalViMode::motionToAfterParagraph()
2945 {
2946     KTextEditor::Cursor c(m_view->cursorPosition());
2947 
2948     int line = c.line();
2949 
2950     m_stickyColumn = -1;
2951 
2952     for (int i = 0; i < getCount(); i++) {
2953         // advance at least one line, but if there are consecutive blank lines
2954         // skip them all
2955         do {
2956             line++;
2957         } while (line <= doc()->lines() - 1 && getLine(line - 1).length() == 0);
2958         while (line < doc()->lines() - 1 && getLine(line).length() != 0) {
2959             line++;
2960         }
2961     }
2962 
2963     if (line >= doc()->lines()) {
2964         line = doc()->lines() - 1;
2965     }
2966 
2967     // if we ended up on the last line, the cursor should be placed on the last column
2968     int column = (line == doc()->lines() - 1) ? qMax(getLine(line).length() - 1, 0) : 0;
2969 
2970     return Range(line, column, InclusiveMotion);
2971 }
2972 
motionToIncrementalSearchMatch()2973 Range NormalViMode::motionToIncrementalSearchMatch()
2974 {
2975     return Range(m_positionWhenIncrementalSearchBegan.line(),
2976                  m_positionWhenIncrementalSearchBegan.column(),
2977                  m_view->cursorPosition().line(),
2978                  m_view->cursorPosition().column(),
2979                  ExclusiveMotion);
2980 }
2981 
2982 ////////////////////////////////////////////////////////////////////////////////
2983 // TEXT OBJECTS
2984 ////////////////////////////////////////////////////////////////////////////////
2985 
textObjectAWord()2986 Range NormalViMode::textObjectAWord()
2987 {
2988     KTextEditor::Cursor c(m_view->cursorPosition());
2989 
2990     KTextEditor::Cursor c1 = c;
2991 
2992     bool startedOnSpace = false;
2993     if (doc()->characterAt(c).isSpace()) {
2994         startedOnSpace = true;
2995     } else {
2996         c1 = findPrevWordStart(c.line(), c.column() + 1, true);
2997         if (!c1.isValid()) {
2998             c1 = KTextEditor::Cursor(0, 0);
2999         }
3000     }
3001     KTextEditor::Cursor c2 = KTextEditor::Cursor(c.line(), c.column() - 1);
3002     for (int i = 1; i <= getCount(); i++) {
3003         c2 = findWordEnd(c2.line(), c2.column());
3004     }
3005     if (!c1.isValid() || !c2.isValid()) {
3006         return Range::invalid();
3007     }
3008     // Adhere to some of Vim's bizarre rules of whether to swallow ensuing spaces or not.
3009     // Don't ask ;)
3010     const KTextEditor::Cursor nextWordStart = findNextWordStart(c2.line(), c2.column());
3011     if (nextWordStart.isValid() && nextWordStart.line() == c2.line()) {
3012         if (!startedOnSpace) {
3013             c2 = KTextEditor::Cursor(nextWordStart.line(), nextWordStart.column() - 1);
3014         }
3015     } else {
3016         c2 = KTextEditor::Cursor(c2.line(), doc()->lineLength(c2.line()) - 1);
3017     }
3018     bool swallowCarriageReturnAtEndOfLine = false;
3019     if (c2.line() != c.line() && c2.column() == doc()->lineLength(c2.line()) - 1) {
3020         // Greedily descend to the next line, so as to swallow the carriage return on this line.
3021         c2 = KTextEditor::Cursor(c2.line() + 1, 0);
3022         swallowCarriageReturnAtEndOfLine = true;
3023     }
3024     const bool swallowPrecedingSpaces =
3025         (c2.column() == doc()->lineLength(c2.line()) - 1 && !doc()->characterAt(c2).isSpace()) || startedOnSpace || swallowCarriageReturnAtEndOfLine;
3026     if (swallowPrecedingSpaces) {
3027         if (c1.column() != 0) {
3028             const KTextEditor::Cursor previousNonSpace = findPrevWordEnd(c.line(), c.column());
3029             if (previousNonSpace.isValid() && previousNonSpace.line() == c1.line()) {
3030                 c1 = KTextEditor::Cursor(previousNonSpace.line(), previousNonSpace.column() + 1);
3031             } else if (startedOnSpace || swallowCarriageReturnAtEndOfLine) {
3032                 c1 = KTextEditor::Cursor(c1.line(), 0);
3033             }
3034         }
3035     }
3036 
3037     return Range(c1, c2, !swallowCarriageReturnAtEndOfLine ? InclusiveMotion : ExclusiveMotion);
3038 }
3039 
textObjectInnerWord()3040 Range NormalViMode::textObjectInnerWord()
3041 {
3042     KTextEditor::Cursor c(m_view->cursorPosition());
3043 
3044     KTextEditor::Cursor c1 = findPrevWordStart(c.line(), c.column() + 1, true);
3045     if (!c1.isValid()) {
3046         c1 = KTextEditor::Cursor(0, 0);
3047     }
3048     // need to start search in column-1 because it might be a one-character word
3049     KTextEditor::Cursor c2(c.line(), c.column() - 1);
3050 
3051     for (int i = 0; i < getCount(); i++) {
3052         c2 = findWordEnd(c2.line(), c2.column(), true);
3053     }
3054 
3055     if (!c2.isValid()) {
3056         c2 = doc()->documentEnd();
3057     }
3058 
3059     // sanity check
3060     if (c1.line() != c2.line() || c1.column() > c2.column()) {
3061         return Range::invalid();
3062     }
3063     return Range(c1, c2, InclusiveMotion);
3064 }
3065 
textObjectAWORD()3066 Range NormalViMode::textObjectAWORD()
3067 {
3068     KTextEditor::Cursor c(m_view->cursorPosition());
3069 
3070     KTextEditor::Cursor c1 = c;
3071 
3072     bool startedOnSpace = false;
3073     if (doc()->characterAt(c).isSpace()) {
3074         startedOnSpace = true;
3075     } else {
3076         c1 = findPrevWORDStart(c.line(), c.column() + 1, true);
3077         if (!c1.isValid()) {
3078             c1 = KTextEditor::Cursor(0, 0);
3079         }
3080     }
3081     KTextEditor::Cursor c2 = KTextEditor::Cursor(c.line(), c.column() - 1);
3082     for (int i = 1; i <= getCount(); i++) {
3083         c2 = findWORDEnd(c2.line(), c2.column());
3084     }
3085     if (!c1.isValid() || !c2.isValid()) {
3086         return Range::invalid();
3087     }
3088     // Adhere to some of Vim's bizarre rules of whether to swallow ensuing spaces or not.
3089     // Don't ask ;)
3090     const KTextEditor::Cursor nextWordStart = findNextWordStart(c2.line(), c2.column());
3091     if (nextWordStart.isValid() && nextWordStart.line() == c2.line()) {
3092         if (!startedOnSpace) {
3093             c2 = KTextEditor::Cursor(nextWordStart.line(), nextWordStart.column() - 1);
3094         }
3095     } else {
3096         c2 = KTextEditor::Cursor(c2.line(), doc()->lineLength(c2.line()) - 1);
3097     }
3098     bool swallowCarriageReturnAtEndOfLine = false;
3099     if (c2.line() != c.line() && c2.column() == doc()->lineLength(c2.line()) - 1) {
3100         // Greedily descend to the next line, so as to swallow the carriage return on this line.
3101         c2 = KTextEditor::Cursor(c2.line() + 1, 0);
3102         swallowCarriageReturnAtEndOfLine = true;
3103     }
3104     const bool swallowPrecedingSpaces =
3105         (c2.column() == doc()->lineLength(c2.line()) - 1 && !doc()->characterAt(c2).isSpace()) || startedOnSpace || swallowCarriageReturnAtEndOfLine;
3106     if (swallowPrecedingSpaces) {
3107         if (c1.column() != 0) {
3108             const KTextEditor::Cursor previousNonSpace = findPrevWORDEnd(c.line(), c.column());
3109             if (previousNonSpace.isValid() && previousNonSpace.line() == c1.line()) {
3110                 c1 = KTextEditor::Cursor(previousNonSpace.line(), previousNonSpace.column() + 1);
3111             } else if (startedOnSpace || swallowCarriageReturnAtEndOfLine) {
3112                 c1 = KTextEditor::Cursor(c1.line(), 0);
3113             }
3114         }
3115     }
3116 
3117     return Range(c1, c2, !swallowCarriageReturnAtEndOfLine ? InclusiveMotion : ExclusiveMotion);
3118 }
3119 
textObjectInnerWORD()3120 Range NormalViMode::textObjectInnerWORD()
3121 {
3122     KTextEditor::Cursor c(m_view->cursorPosition());
3123 
3124     KTextEditor::Cursor c1 = findPrevWORDStart(c.line(), c.column() + 1, true);
3125     if (!c1.isValid()) {
3126         c1 = KTextEditor::Cursor(0, 0);
3127     }
3128     KTextEditor::Cursor c2(c);
3129 
3130     for (int i = 0; i < getCount(); i++) {
3131         c2 = findWORDEnd(c2.line(), c2.column(), true);
3132     }
3133 
3134     if (!c2.isValid()) {
3135         c2 = doc()->documentEnd();
3136     }
3137 
3138     // sanity check
3139     if (c1.line() != c2.line() || c1.column() > c2.column()) {
3140         return Range::invalid();
3141     }
3142     return Range(c1, c2, InclusiveMotion);
3143 }
3144 
findSentenceStart()3145 KTextEditor::Cursor NormalViMode::findSentenceStart()
3146 {
3147     KTextEditor::Cursor c(m_view->cursorPosition());
3148     int linenum = c.line();
3149     int column = c.column();
3150     int prev = column;
3151 
3152     for (int i = linenum; i >= 0; i--) {
3153         const QString &line = doc()->line(i);
3154         const int lineLength = line.size();
3155         if (i != linenum) {
3156             column = lineLength;
3157         }
3158 
3159         // An empty line is the end of a paragraph.
3160         if (line.isEmpty()) {
3161             return KTextEditor::Cursor((i != linenum) ? i + 1 : i, prev);
3162         }
3163 
3164         prev = column;
3165         for (int j = column; j >= 0; j--) {
3166             if (j == lineLength || line.at(j).isSpace()) {
3167                 int lastSpace = j--;
3168                 for (; j >= 0 && QStringLiteral("\"')]").indexOf(line.at(j)) != -1; j--) {
3169                     ;
3170                 }
3171 
3172                 if (j >= 0 && QStringLiteral(".!?").indexOf(line.at(j)) != -1) {
3173                     if (lastSpace == lineLength) {
3174                         // If the line ends with one of .!?, then the sentence starts from the next line.
3175                         return KTextEditor::Cursor(i + 1, 0);
3176                     }
3177 
3178                     return KTextEditor::Cursor(i, prev);
3179                 }
3180                 j = lastSpace;
3181             } else {
3182                 prev = j;
3183             }
3184         }
3185     }
3186 
3187     return KTextEditor::Cursor(0, 0);
3188 }
3189 
findSentenceEnd()3190 KTextEditor::Cursor NormalViMode::findSentenceEnd()
3191 {
3192     KTextEditor::Cursor c(m_view->cursorPosition());
3193     int linenum = c.line();
3194     int column = c.column();
3195     int j = 0;
3196     int prev = 0;
3197 
3198     for (int i = linenum; i < doc()->lines(); i++) {
3199         const QString &line = doc()->line(i);
3200 
3201         // An empty line is the end of a paragraph.
3202         if (line.isEmpty()) {
3203             return KTextEditor::Cursor(linenum, j);
3204         }
3205 
3206         // Iterating over the line to reach any '.', '!', '?'
3207         for (j = column; j < line.size(); j++) {
3208             if (QStringLiteral(".!?").indexOf(line.at(j)) != -1) {
3209                 prev = j++;
3210                 // Skip possible closing characters.
3211                 for (; j < line.size() && QStringLiteral("\"')]").indexOf(line.at(j)) != -1; j++) {
3212                     ;
3213                 }
3214 
3215                 if (j >= line.size()) {
3216                     return KTextEditor::Cursor(i, j - 1);
3217                 }
3218 
3219                 // And hopefully we're done...
3220                 if (line.at(j).isSpace()) {
3221                     return KTextEditor::Cursor(i, j - 1);
3222                 }
3223                 j = prev;
3224             }
3225         }
3226         linenum = i;
3227         prev = column;
3228         column = 0;
3229     }
3230 
3231     return KTextEditor::Cursor(linenum, j - 1);
3232 }
3233 
findParagraphStart()3234 KTextEditor::Cursor NormalViMode::findParagraphStart()
3235 {
3236     KTextEditor::Cursor c(m_view->cursorPosition());
3237     const bool firstBlank = doc()->line(c.line()).isEmpty();
3238     int prev = c.line();
3239 
3240     for (int i = prev; i >= 0; i--) {
3241         if (doc()->line(i).isEmpty()) {
3242             if (i != prev) {
3243                 prev = i + 1;
3244             }
3245 
3246             /* Skip consecutive empty lines. */
3247             if (firstBlank) {
3248                 i--;
3249                 for (; i >= 0 && doc()->line(i).isEmpty(); i--, prev--) {
3250                     ;
3251                 }
3252             }
3253             return KTextEditor::Cursor(prev, 0);
3254         }
3255     }
3256     return KTextEditor::Cursor(0, 0);
3257 }
3258 
findParagraphEnd()3259 KTextEditor::Cursor NormalViMode::findParagraphEnd()
3260 {
3261     KTextEditor::Cursor c(m_view->cursorPosition());
3262     int prev = c.line();
3263     int lines = doc()->lines();
3264     const bool firstBlank = doc()->line(prev).isEmpty();
3265 
3266     for (int i = prev; i < lines; i++) {
3267         if (doc()->line(i).isEmpty()) {
3268             if (i != prev) {
3269                 prev = i - 1;
3270             }
3271 
3272             /* Skip consecutive empty lines. */
3273             if (firstBlank) {
3274                 i++;
3275                 for (; i < lines && doc()->line(i).isEmpty(); i++, prev++) {
3276                     ;
3277                 }
3278             }
3279             int length = doc()->lineLength(prev);
3280             return KTextEditor::Cursor(prev, (length <= 0) ? 0 : length - 1);
3281         }
3282     }
3283     return doc()->documentEnd();
3284 }
3285 
textObjectInnerSentence()3286 Range NormalViMode::textObjectInnerSentence()
3287 {
3288     Range r;
3289     KTextEditor::Cursor c1 = findSentenceStart();
3290     KTextEditor::Cursor c2 = findSentenceEnd();
3291     updateCursor(c1);
3292 
3293     r.startLine = c1.line();
3294     r.startColumn = c1.column();
3295     r.endLine = c2.line();
3296     r.endColumn = c2.column();
3297     return r;
3298 }
3299 
textObjectASentence()3300 Range NormalViMode::textObjectASentence()
3301 {
3302     int i;
3303     Range r = textObjectInnerSentence();
3304     const QString &line = doc()->line(r.endLine);
3305 
3306     // Skip whitespaces and tabs.
3307     for (i = r.endColumn + 1; i < line.size(); i++) {
3308         if (!line.at(i).isSpace()) {
3309             break;
3310         }
3311     }
3312     r.endColumn = i - 1;
3313 
3314     // Remove preceding spaces.
3315     if (r.startColumn != 0) {
3316         if (r.endColumn == line.size() - 1 && !line.at(r.endColumn).isSpace()) {
3317             const QString &line = doc()->line(r.startLine);
3318             for (i = r.startColumn - 1; i >= 0; i--) {
3319                 if (!line.at(i).isSpace()) {
3320                     break;
3321                 }
3322             }
3323             r.startColumn = i + 1;
3324         }
3325     }
3326     return r;
3327 }
3328 
textObjectInnerParagraph()3329 Range NormalViMode::textObjectInnerParagraph()
3330 {
3331     Range r;
3332     KTextEditor::Cursor c1 = findParagraphStart();
3333     KTextEditor::Cursor c2 = findParagraphEnd();
3334     updateCursor(c1);
3335 
3336     r.startLine = c1.line();
3337     r.startColumn = c1.column();
3338     r.endLine = c2.line();
3339     r.endColumn = c2.column();
3340     return r;
3341 }
3342 
textObjectAParagraph()3343 Range NormalViMode::textObjectAParagraph()
3344 {
3345     Range r = textObjectInnerParagraph();
3346     int lines = doc()->lines();
3347 
3348     if (r.endLine + 1 < lines) {
3349         // If the next line is empty, remove all subsequent empty lines.
3350         // Otherwise we'll grab the next paragraph.
3351         if (doc()->line(r.endLine + 1).isEmpty()) {
3352             for (int i = r.endLine + 1; i < lines && doc()->line(i).isEmpty(); i++) {
3353                 r.endLine++;
3354             }
3355             r.endColumn = 0;
3356         } else {
3357             KTextEditor::Cursor prev = m_view->cursorPosition();
3358             KTextEditor::Cursor c(r.endLine + 1, 0);
3359             updateCursor(c);
3360             c = findParagraphEnd();
3361             updateCursor(prev);
3362             r.endLine = c.line();
3363             r.endColumn = c.column();
3364         }
3365     } else if (doc()->lineLength(r.startLine) > 0) {
3366         // We went too far, but maybe we can grab previous empty lines.
3367         for (int i = r.startLine - 1; i >= 0 && doc()->line(i).isEmpty(); i--) {
3368             r.startLine--;
3369         }
3370         r.startColumn = 0;
3371         updateCursor(KTextEditor::Cursor(r.startLine, r.startColumn));
3372     } else {
3373         // We went too far and we're on empty lines, do nothing.
3374         return Range::invalid();
3375     }
3376     return r;
3377 }
3378 
textObjectAQuoteDouble()3379 Range NormalViMode::textObjectAQuoteDouble()
3380 {
3381     return findSurroundingQuotes(QLatin1Char('"'), false);
3382 }
3383 
textObjectInnerQuoteDouble()3384 Range NormalViMode::textObjectInnerQuoteDouble()
3385 {
3386     return findSurroundingQuotes(QLatin1Char('"'), true);
3387 }
3388 
textObjectAQuoteSingle()3389 Range NormalViMode::textObjectAQuoteSingle()
3390 {
3391     return findSurroundingQuotes(QLatin1Char('\''), false);
3392 }
3393 
textObjectInnerQuoteSingle()3394 Range NormalViMode::textObjectInnerQuoteSingle()
3395 {
3396     return findSurroundingQuotes(QLatin1Char('\''), true);
3397 }
3398 
textObjectABackQuote()3399 Range NormalViMode::textObjectABackQuote()
3400 {
3401     return findSurroundingQuotes(QLatin1Char('`'), false);
3402 }
3403 
textObjectInnerBackQuote()3404 Range NormalViMode::textObjectInnerBackQuote()
3405 {
3406     return findSurroundingQuotes(QLatin1Char('`'), true);
3407 }
3408 
textObjectAParen()3409 Range NormalViMode::textObjectAParen()
3410 {
3411     return findSurroundingBrackets(QLatin1Char('('), QLatin1Char(')'), false, QLatin1Char('('), QLatin1Char(')'));
3412 }
3413 
textObjectInnerParen()3414 Range NormalViMode::textObjectInnerParen()
3415 {
3416     return findSurroundingBrackets(QLatin1Char('('), QLatin1Char(')'), true, QLatin1Char('('), QLatin1Char(')'));
3417 }
3418 
textObjectABracket()3419 Range NormalViMode::textObjectABracket()
3420 {
3421     return findSurroundingBrackets(QLatin1Char('['), QLatin1Char(']'), false, QLatin1Char('['), QLatin1Char(']'));
3422 }
3423 
textObjectInnerBracket()3424 Range NormalViMode::textObjectInnerBracket()
3425 {
3426     return findSurroundingBrackets(QLatin1Char('['), QLatin1Char(']'), true, QLatin1Char('['), QLatin1Char(']'));
3427 }
3428 
textObjectACurlyBracket()3429 Range NormalViMode::textObjectACurlyBracket()
3430 {
3431     return findSurroundingBrackets(QLatin1Char('{'), QLatin1Char('}'), false, QLatin1Char('{'), QLatin1Char('}'));
3432 }
3433 
textObjectInnerCurlyBracket()3434 Range NormalViMode::textObjectInnerCurlyBracket()
3435 {
3436     const Range allBetweenCurlyBrackets = findSurroundingBrackets(QLatin1Char('{'), QLatin1Char('}'), true, QLatin1Char('{'), QLatin1Char('}'));
3437     // Emulate the behaviour of vim, which tries to leave the closing bracket on its own line
3438     // if it was originally on a line different to that of the opening bracket.
3439     Range innerCurlyBracket(allBetweenCurlyBrackets);
3440 
3441     if (innerCurlyBracket.startLine != innerCurlyBracket.endLine) {
3442         const bool openingBraceIsLastCharOnLine = innerCurlyBracket.startColumn == doc()->line(innerCurlyBracket.startLine).length();
3443         const bool stuffToDeleteIsAllOnEndLine = openingBraceIsLastCharOnLine && innerCurlyBracket.endLine == innerCurlyBracket.startLine + 1;
3444         const QString textLeadingClosingBracket = doc()->line(innerCurlyBracket.endLine).mid(0, innerCurlyBracket.endColumn + 1);
3445         const bool closingBracketHasLeadingNonWhitespace = !textLeadingClosingBracket.trimmed().isEmpty();
3446         if (stuffToDeleteIsAllOnEndLine) {
3447             if (!closingBracketHasLeadingNonWhitespace) {
3448                 // Nothing there to select - abort.
3449                 return Range::invalid();
3450             } else {
3451                 // Shift the beginning of the range to the start of the line containing the closing bracket.
3452                 innerCurlyBracket.startLine++;
3453                 innerCurlyBracket.startColumn = 0;
3454             }
3455         } else {
3456             if (openingBraceIsLastCharOnLine && !closingBracketHasLeadingNonWhitespace) {
3457                 innerCurlyBracket.startLine++;
3458                 innerCurlyBracket.startColumn = 0;
3459                 m_lastMotionWasLinewiseInnerBlock = true;
3460             }
3461             {
3462                 // The line containing the end bracket is left alone if the end bracket is preceded by just whitespace,
3463                 // else we need to delete everything (i.e. end up with "{}")
3464                 if (!closingBracketHasLeadingNonWhitespace) {
3465                     // Shrink the endpoint of the range so that it ends at the end of the line above,
3466                     // leaving the closing bracket on its own line.
3467                     innerCurlyBracket.endLine--;
3468                     innerCurlyBracket.endColumn = doc()->line(innerCurlyBracket.endLine).length();
3469                 }
3470             }
3471         }
3472     }
3473     return innerCurlyBracket;
3474 }
3475 
textObjectAInequalitySign()3476 Range NormalViMode::textObjectAInequalitySign()
3477 {
3478     return findSurroundingBrackets(QLatin1Char('<'), QLatin1Char('>'), false, QLatin1Char('<'), QLatin1Char('>'));
3479 }
3480 
textObjectInnerInequalitySign()3481 Range NormalViMode::textObjectInnerInequalitySign()
3482 {
3483     return findSurroundingBrackets(QLatin1Char('<'), QLatin1Char('>'), true, QLatin1Char('<'), QLatin1Char('>'));
3484 }
3485 
textObjectAComma()3486 Range NormalViMode::textObjectAComma()
3487 {
3488     return textObjectComma(false);
3489 }
3490 
textObjectInnerComma()3491 Range NormalViMode::textObjectInnerComma()
3492 {
3493     return textObjectComma(true);
3494 }
3495 
3496 // add commands
3497 // when adding commands here, remember to add them to visual mode too (if applicable)
initializeCommands()3498 void NormalViMode::initializeCommands()
3499 {
3500     ADDCMD("a", commandEnterInsertModeAppend, IS_CHANGE);
3501     ADDCMD("A", commandEnterInsertModeAppendEOL, IS_CHANGE);
3502     ADDCMD("i", commandEnterInsertMode, IS_CHANGE);
3503     ADDCMD("<insert>", commandEnterInsertMode, IS_CHANGE);
3504     ADDCMD("I", commandEnterInsertModeBeforeFirstNonBlankInLine, IS_CHANGE);
3505     ADDCMD("gi", commandEnterInsertModeLast, IS_CHANGE);
3506     ADDCMD("v", commandEnterVisualMode, 0);
3507     ADDCMD("V", commandEnterVisualLineMode, 0);
3508     ADDCMD("<c-v>", commandEnterVisualBlockMode, 0);
3509     ADDCMD("gv", commandReselectVisual, SHOULD_NOT_RESET);
3510     ADDCMD("o", commandOpenNewLineUnder, IS_CHANGE);
3511     ADDCMD("O", commandOpenNewLineOver, IS_CHANGE);
3512     ADDCMD("J", commandJoinLines, IS_CHANGE);
3513     ADDCMD("c", commandChange, IS_CHANGE | NEEDS_MOTION);
3514     ADDCMD("C", commandChangeToEOL, IS_CHANGE);
3515     ADDCMD("cc", commandChangeLine, IS_CHANGE);
3516     ADDCMD("s", commandSubstituteChar, IS_CHANGE);
3517     ADDCMD("S", commandSubstituteLine, IS_CHANGE);
3518     ADDCMD("dd", commandDeleteLine, IS_CHANGE);
3519     ADDCMD("d", commandDelete, IS_CHANGE | NEEDS_MOTION);
3520     ADDCMD("D", commandDeleteToEOL, IS_CHANGE);
3521     ADDCMD("x", commandDeleteChar, IS_CHANGE);
3522     ADDCMD("<delete>", commandDeleteChar, IS_CHANGE);
3523     ADDCMD("X", commandDeleteCharBackward, IS_CHANGE);
3524     ADDCMD("gu", commandMakeLowercase, IS_CHANGE | NEEDS_MOTION);
3525     ADDCMD("guu", commandMakeLowercaseLine, IS_CHANGE);
3526     ADDCMD("gU", commandMakeUppercase, IS_CHANGE | NEEDS_MOTION);
3527     ADDCMD("gUU", commandMakeUppercaseLine, IS_CHANGE);
3528     ADDCMD("y", commandYank, NEEDS_MOTION);
3529     ADDCMD("yy", commandYankLine, 0);
3530     ADDCMD("Y", commandYankToEOL, 0);
3531     ADDCMD("p", commandPaste, IS_CHANGE);
3532     ADDCMD("P", commandPasteBefore, IS_CHANGE);
3533     ADDCMD("gp", commandgPaste, IS_CHANGE);
3534     ADDCMD("gP", commandgPasteBefore, IS_CHANGE);
3535     ADDCMD("]p", commandIndentedPaste, IS_CHANGE);
3536     ADDCMD("[p", commandIndentedPasteBefore, IS_CHANGE);
3537     ADDCMD("r.", commandReplaceCharacter, IS_CHANGE | REGEX_PATTERN);
3538     ADDCMD("R", commandEnterReplaceMode, IS_CHANGE);
3539     ADDCMD(":", commandSwitchToCmdLine, 0);
3540     ADDCMD("u", commandUndo, 0);
3541     ADDCMD("<c-r>", commandRedo, 0);
3542     ADDCMD("U", commandRedo, 0);
3543     ADDCMD("m.", commandSetMark, REGEX_PATTERN);
3544     ADDCMD(">>", commandIndentLine, IS_CHANGE);
3545     ADDCMD("<<", commandUnindentLine, IS_CHANGE);
3546     ADDCMD(">", commandIndentLines, IS_CHANGE | NEEDS_MOTION);
3547     ADDCMD("<", commandUnindentLines, IS_CHANGE | NEEDS_MOTION);
3548     ADDCMD("<c-f>", commandScrollPageDown, 0);
3549     ADDCMD("<pagedown>", commandScrollPageDown, 0);
3550     ADDCMD("<c-b>", commandScrollPageUp, 0);
3551     ADDCMD("<pageup>", commandScrollPageUp, 0);
3552     ADDCMD("<c-u>", commandScrollHalfPageUp, 0);
3553     ADDCMD("<c-d>", commandScrollHalfPageDown, 0);
3554     ADDCMD("z.", commandCenterViewOnNonBlank, 0);
3555     ADDCMD("zz", commandCenterViewOnCursor, 0);
3556     ADDCMD("z<return>", commandTopViewOnNonBlank, 0);
3557     ADDCMD("zt", commandTopViewOnCursor, 0);
3558     ADDCMD("z-", commandBottomViewOnNonBlank, 0);
3559     ADDCMD("zb", commandBottomViewOnCursor, 0);
3560     ADDCMD("ga", commandPrintCharacterCode, SHOULD_NOT_RESET);
3561     ADDCMD(".", commandRepeatLastChange, 0);
3562     ADDCMD("==", commandAlignLine, IS_CHANGE);
3563     ADDCMD("=", commandAlignLines, IS_CHANGE | NEEDS_MOTION);
3564     ADDCMD("~", commandChangeCase, IS_CHANGE);
3565     ADDCMD("g~", commandChangeCaseRange, IS_CHANGE | NEEDS_MOTION);
3566     ADDCMD("g~~", commandChangeCaseLine, IS_CHANGE);
3567     ADDCMD("<c-a>", commandAddToNumber, IS_CHANGE);
3568     ADDCMD("<c-x>", commandSubtractFromNumber, IS_CHANGE);
3569     ADDCMD("<c-o>", commandGoToPrevJump, CAN_LAND_INSIDE_FOLDING_RANGE);
3570     ADDCMD("<c-i>", commandGoToNextJump, CAN_LAND_INSIDE_FOLDING_RANGE);
3571 
3572     ADDCMD("<c-w>h", commandSwitchToLeftView, 0);
3573     ADDCMD("<c-w><c-h>", commandSwitchToLeftView, 0);
3574     ADDCMD("<c-w><left>", commandSwitchToLeftView, 0);
3575     ADDCMD("<c-w>j", commandSwitchToDownView, 0);
3576     ADDCMD("<c-w><c-j>", commandSwitchToDownView, 0);
3577     ADDCMD("<c-w><down>", commandSwitchToDownView, 0);
3578     ADDCMD("<c-w>k", commandSwitchToUpView, 0);
3579     ADDCMD("<c-w><c-k>", commandSwitchToUpView, 0);
3580     ADDCMD("<c-w><up>", commandSwitchToUpView, 0);
3581     ADDCMD("<c-w>l", commandSwitchToRightView, 0);
3582     ADDCMD("<c-w><c-l>", commandSwitchToRightView, 0);
3583     ADDCMD("<c-w><right>", commandSwitchToRightView, 0);
3584     ADDCMD("<c-w>w", commandSwitchToNextView, 0);
3585     ADDCMD("<c-w><c-w>", commandSwitchToNextView, 0);
3586 
3587     ADDCMD("<c-w>s", commandSplitHoriz, 0);
3588     ADDCMD("<c-w>S", commandSplitHoriz, 0);
3589     ADDCMD("<c-w><c-s>", commandSplitHoriz, 0);
3590     ADDCMD("<c-w>v", commandSplitVert, 0);
3591     ADDCMD("<c-w><c-v>", commandSplitVert, 0);
3592     ADDCMD("<c-w>c", commandCloseView, 0);
3593 
3594     ADDCMD("gt", commandSwitchToNextTab, 0);
3595     ADDCMD("gT", commandSwitchToPrevTab, 0);
3596 
3597     ADDCMD("gqq", commandFormatLine, IS_CHANGE);
3598     ADDCMD("gq", commandFormatLines, IS_CHANGE | NEEDS_MOTION);
3599 
3600     ADDCMD("zo", commandExpandLocal, 0);
3601     ADDCMD("zc", commandCollapseLocal, 0);
3602     ADDCMD("za", commandToggleRegionVisibility, 0);
3603     ADDCMD("zr", commandExpandAll, 0);
3604     ADDCMD("zm", commandCollapseToplevelNodes, 0);
3605 
3606     ADDCMD("q.", commandStartRecordingMacro, REGEX_PATTERN);
3607     ADDCMD("@.", commandReplayMacro, REGEX_PATTERN);
3608 
3609     ADDCMD("ZZ", commandCloseWrite, 0);
3610     ADDCMD("ZQ", commandCloseNocheck, 0);
3611 
3612     // regular motions
3613     ADDMOTION("h", motionLeft, 0);
3614     ADDMOTION("<left>", motionLeft, 0);
3615     ADDMOTION("<backspace>", motionLeft, 0);
3616     ADDMOTION("j", motionDown, 0);
3617     ADDMOTION("<down>", motionDown, 0);
3618     ADDMOTION("<enter>", motionDownToFirstNonBlank, 0);
3619     ADDMOTION("<return>", motionDownToFirstNonBlank, 0);
3620     ADDMOTION("k", motionUp, 0);
3621     ADDMOTION("<up>", motionUp, 0);
3622     ADDMOTION("-", motionUpToFirstNonBlank, 0);
3623     ADDMOTION("l", motionRight, 0);
3624     ADDMOTION("<right>", motionRight, 0);
3625     ADDMOTION(" ", motionRight, 0);
3626     ADDMOTION("$", motionToEOL, 0);
3627     ADDMOTION("<end>", motionToEOL, 0);
3628     ADDMOTION("0", motionToColumn0, 0);
3629     ADDMOTION("<home>", motionToColumn0, 0);
3630     ADDMOTION("^", motionToFirstCharacterOfLine, 0);
3631     ADDMOTION("f.", motionFindChar, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE);
3632     ADDMOTION("F.", motionFindCharBackward, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE);
3633     ADDMOTION("t.", motionToChar, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE);
3634     ADDMOTION("T.", motionToCharBackward, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE);
3635     ADDMOTION(";", motionRepeatlastTF, CAN_LAND_INSIDE_FOLDING_RANGE);
3636     ADDMOTION(",", motionRepeatlastTFBackward, CAN_LAND_INSIDE_FOLDING_RANGE);
3637     ADDMOTION("n", motionFindNext, CAN_LAND_INSIDE_FOLDING_RANGE);
3638     ADDMOTION("N", motionFindPrev, CAN_LAND_INSIDE_FOLDING_RANGE);
3639     ADDMOTION("gg", motionToLineFirst, 0);
3640     ADDMOTION("G", motionToLineLast, 0);
3641     ADDMOTION("w", motionWordForward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3642     ADDMOTION("W", motionWORDForward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3643     ADDMOTION("<c-right>", motionWordForward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3644     ADDMOTION("<c-left>", motionWordBackward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3645     ADDMOTION("b", motionWordBackward, CAN_LAND_INSIDE_FOLDING_RANGE);
3646     ADDMOTION("B", motionWORDBackward, CAN_LAND_INSIDE_FOLDING_RANGE);
3647     ADDMOTION("e", motionToEndOfWord, CAN_LAND_INSIDE_FOLDING_RANGE);
3648     ADDMOTION("E", motionToEndOfWORD, CAN_LAND_INSIDE_FOLDING_RANGE);
3649     ADDMOTION("ge", motionToEndOfPrevWord, CAN_LAND_INSIDE_FOLDING_RANGE);
3650     ADDMOTION("gE", motionToEndOfPrevWORD, CAN_LAND_INSIDE_FOLDING_RANGE);
3651     ADDMOTION("|", motionToScreenColumn, 0);
3652     ADDMOTION("%", motionToMatchingItem, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3653     ADDMOTION("`[a-zA-Z^><\\.\\[\\]]", motionToMark, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE);
3654     ADDMOTION("'[a-zA-Z^><]", motionToMarkLine, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE);
3655     ADDMOTION("[[", motionToPreviousBraceBlockStart, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3656     ADDMOTION("]]", motionToNextBraceBlockStart, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3657     ADDMOTION("[]", motionToPreviousBraceBlockEnd, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3658     ADDMOTION("][", motionToNextBraceBlockEnd, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3659     ADDMOTION("*", motionToNextOccurrence, CAN_LAND_INSIDE_FOLDING_RANGE);
3660     ADDMOTION("#", motionToPrevOccurrence, CAN_LAND_INSIDE_FOLDING_RANGE);
3661     ADDMOTION("H", motionToFirstLineOfWindow, 0);
3662     ADDMOTION("M", motionToMiddleLineOfWindow, 0);
3663     ADDMOTION("L", motionToLastLineOfWindow, 0);
3664     ADDMOTION("gj", motionToNextVisualLine, 0);
3665     ADDMOTION("g<down>", motionToNextVisualLine, 0);
3666     ADDMOTION("gk", motionToPrevVisualLine, 0);
3667     ADDMOTION("g<up>", motionToPrevVisualLine, 0);
3668     ADDMOTION("(", motionToPreviousSentence, CAN_LAND_INSIDE_FOLDING_RANGE);
3669     ADDMOTION(")", motionToNextSentence, CAN_LAND_INSIDE_FOLDING_RANGE);
3670     ADDMOTION("{", motionToBeforeParagraph, CAN_LAND_INSIDE_FOLDING_RANGE);
3671     ADDMOTION("}", motionToAfterParagraph, CAN_LAND_INSIDE_FOLDING_RANGE);
3672 
3673     // text objects
3674     ADDMOTION("iw", textObjectInnerWord, 0);
3675     ADDMOTION("aw", textObjectAWord, IS_NOT_LINEWISE);
3676     ADDMOTION("iW", textObjectInnerWORD, 0);
3677     ADDMOTION("aW", textObjectAWORD, IS_NOT_LINEWISE);
3678     ADDMOTION("is", textObjectInnerSentence, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3679     ADDMOTION("as", textObjectASentence, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3680     ADDMOTION("ip", textObjectInnerParagraph, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3681     ADDMOTION("ap", textObjectAParagraph, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3682     ADDMOTION("i\"", textObjectInnerQuoteDouble, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3683     ADDMOTION("a\"", textObjectAQuoteDouble, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3684     ADDMOTION("i'", textObjectInnerQuoteSingle, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3685     ADDMOTION("a'", textObjectAQuoteSingle, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3686     ADDMOTION("i`", textObjectInnerBackQuote, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3687     ADDMOTION("a`", textObjectABackQuote, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3688     ADDMOTION("i[()b]", textObjectInnerParen, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3689     ADDMOTION("a[()b]", textObjectAParen, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3690     ADDMOTION("i[{}B]", textObjectInnerCurlyBracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3691     ADDMOTION("a[{}B]", textObjectACurlyBracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3692     ADDMOTION("i[><]", textObjectInnerInequalitySign, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3693     ADDMOTION("a[><]", textObjectAInequalitySign, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3694     ADDMOTION("i[\\[\\]]", textObjectInnerBracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3695     ADDMOTION("a[\\[\\]]", textObjectABracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3696     ADDMOTION("i,", textObjectInnerComma, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3697     ADDMOTION("a,", textObjectAComma, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3698 
3699     ADDMOTION("/<enter>", motionToIncrementalSearchMatch, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3700     ADDMOTION("?<enter>", motionToIncrementalSearchMatch, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE);
3701 }
3702 
generateMatchingItemRegex() const3703 QRegularExpression NormalViMode::generateMatchingItemRegex() const
3704 {
3705     QString pattern(QStringLiteral("\\[|\\]|\\{|\\}|\\(|\\)|"));
3706 
3707     for (QString s : std::as_const(m_matchingItems)) {
3708         if (s.startsWith(QLatin1Char('-'))) {
3709             s.remove(0, 1);
3710         }
3711         s.replace(QLatin1Char('*'), QStringLiteral("\\*"));
3712         s.replace(QLatin1Char('+'), QStringLiteral("\\+"));
3713         s.replace(QLatin1Char('['), QStringLiteral("\\["));
3714         s.replace(QLatin1Char(']'), QStringLiteral("\\]"));
3715         s.replace(QLatin1Char('('), QStringLiteral("\\("));
3716         s.replace(QLatin1Char(')'), QStringLiteral("\\)"));
3717         s.replace(QLatin1Char('{'), QStringLiteral("\\{"));
3718         s.replace(QLatin1Char('}'), QStringLiteral("\\}"));
3719 
3720         s.append(QLatin1Char('|'));
3721         pattern.append(s);
3722     }
3723     // remove extra "|" at the end
3724     pattern.chop(1);
3725 
3726     return QRegularExpression(pattern);
3727 }
3728 
3729 // returns the operation mode that should be used. this is decided by using the following heuristic:
3730 // 1. if we're in visual block mode, it should be Block
3731 // 2. if we're in visual line mode OR the range spans several lines, it should be LineWise
3732 // 3. if neither of these is true, CharWise is returned
3733 // 4. there are some motion that makes all operator charwise, if we have one of them mode will be CharWise
getOperationMode() const3734 OperationMode NormalViMode::getOperationMode() const
3735 {
3736     OperationMode m = CharWise;
3737 
3738     if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualBlockMode) {
3739         m = Block;
3740     } else if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualLineMode
3741                || (m_commandRange.startLine != m_commandRange.endLine && m_viInputModeManager->getCurrentViMode() != ViMode::VisualMode)) {
3742         m = LineWise;
3743     }
3744 
3745     if (m_commandWithMotion && !m_linewiseCommand) {
3746         m = CharWise;
3747     }
3748 
3749     if (m_lastMotionWasLinewiseInnerBlock) {
3750         m = LineWise;
3751     }
3752 
3753     return m;
3754 }
3755 
paste(PasteLocation pasteLocation,bool isgPaste,bool isIndentedPaste)3756 bool NormalViMode::paste(PasteLocation pasteLocation, bool isgPaste, bool isIndentedPaste)
3757 {
3758     KTextEditor::Cursor pasteAt(m_view->cursorPosition());
3759     KTextEditor::Cursor cursorAfterPaste = pasteAt;
3760     QChar reg = getChosenRegister(UnnamedRegister);
3761 
3762     OperationMode m = getRegisterFlag(reg);
3763     QString textToInsert = getRegisterContent(reg);
3764     const bool isTextMultiLine = textToInsert.count(QLatin1Char('\n')) > 0;
3765 
3766     // In temporary normal mode, p/P act as gp/gP.
3767     isgPaste |= m_viInputModeManager->getTemporaryNormalMode();
3768 
3769     if (textToInsert.isEmpty()) {
3770         error(i18n("Nothing in register %1", reg.toLower()));
3771         return false;
3772     }
3773 
3774     if (getCount() > 1) {
3775         textToInsert = textToInsert.repeated(getCount()); // FIXME: does this make sense for blocks?
3776     }
3777 
3778     if (m == LineWise) {
3779         pasteAt.setColumn(0);
3780         if (isIndentedPaste) {
3781             // Note that this does indeed work if there is no non-whitespace on the current line or if
3782             // the line is empty!
3783             static const QRegularExpression nonWhitespaceRegex(QStringLiteral("[^\\s]"));
3784             const QString pasteLineString = doc()->line(pasteAt.line());
3785             const QString leadingWhiteSpaceOnCurrentLine = pasteLineString.mid(0, pasteLineString.indexOf(nonWhitespaceRegex));
3786             const QString leadingWhiteSpaceOnFirstPastedLine = textToInsert.mid(0, textToInsert.indexOf(nonWhitespaceRegex));
3787             // QString has no "left trim" method, bizarrely.
3788             while (textToInsert[0].isSpace()) {
3789                 textToInsert = textToInsert.mid(1);
3790             }
3791             textToInsert.prepend(leadingWhiteSpaceOnCurrentLine);
3792             // Remove the last \n, temporarily: we're going to alter the indentation of each pasted line
3793             // by doing a search and replace on '\n's, but don't want to alter this one.
3794             textToInsert.chop(1);
3795             textToInsert.replace(QLatin1Char('\n') + leadingWhiteSpaceOnFirstPastedLine, QLatin1Char('\n') + leadingWhiteSpaceOnCurrentLine);
3796             textToInsert.append(QLatin1Char('\n')); // Re-add the temporarily removed last '\n'.
3797         }
3798         if (pasteLocation == AfterCurrentPosition) {
3799             textToInsert.chop(1); // remove the last \n
3800             pasteAt.setColumn(doc()->lineLength(pasteAt.line())); // paste after the current line and ...
3801             textToInsert.prepend(QLatin1Char('\n')); // ... prepend a \n, so the text starts on a new line
3802 
3803             cursorAfterPaste.setLine(cursorAfterPaste.line() + 1);
3804         }
3805         if (isgPaste) {
3806             cursorAfterPaste.setLine(cursorAfterPaste.line() + textToInsert.count(QLatin1Char('\n')));
3807         }
3808     } else {
3809         if (pasteLocation == AfterCurrentPosition) {
3810             // Move cursor forward one before we paste.  The position after the paste must also
3811             // be updated accordingly.
3812             if (getLine(pasteAt.line()).length() > 0) {
3813                 pasteAt.setColumn(pasteAt.column() + 1);
3814             }
3815             cursorAfterPaste = pasteAt;
3816         }
3817         const bool leaveCursorAtStartOfPaste = isTextMultiLine && !isgPaste;
3818         if (!leaveCursorAtStartOfPaste) {
3819             cursorAfterPaste = cursorPosAtEndOfPaste(pasteAt, textToInsert);
3820             if (!isgPaste) {
3821                 cursorAfterPaste.setColumn(cursorAfterPaste.column() - 1);
3822             }
3823         }
3824     }
3825 
3826     doc()->editBegin();
3827     if (m_view->selection()) {
3828         pasteAt = m_view->selectionRange().start();
3829         doc()->removeText(m_view->selectionRange());
3830     }
3831     doc()->insertText(pasteAt, textToInsert, m == Block);
3832     doc()->editEnd();
3833 
3834     if (cursorAfterPaste.line() >= doc()->lines()) {
3835         cursorAfterPaste.setLine(doc()->lines() - 1);
3836     }
3837     updateCursor(cursorAfterPaste);
3838 
3839     return true;
3840 }
3841 
cursorPosAtEndOfPaste(const KTextEditor::Cursor pasteLocation,const QString & pastedText) const3842 KTextEditor::Cursor NormalViMode::cursorPosAtEndOfPaste(const KTextEditor::Cursor pasteLocation, const QString &pastedText) const
3843 {
3844     KTextEditor::Cursor cAfter = pasteLocation;
3845     const int lineCount = pastedText.count(QLatin1Char('\n')) + 1;
3846     if (lineCount == 1) {
3847         cAfter.setColumn(cAfter.column() + pastedText.length());
3848     } else {
3849         const int lastLineLength = pastedText.size() - (pastedText.lastIndexOf(QLatin1Char('\n')) + 1);
3850         cAfter.setColumn(lastLineLength);
3851         cAfter.setLine(cAfter.line() + lineCount - 1);
3852     }
3853     return cAfter;
3854 }
3855 
joinLines(unsigned int from,unsigned int to) const3856 void NormalViMode::joinLines(unsigned int from, unsigned int to) const
3857 {
3858     // make sure we don't try to join lines past the document end
3859     if (to >= (unsigned int)(doc()->lines())) {
3860         to = doc()->lines() - 1;
3861     }
3862 
3863     // joining one line is a no-op
3864     if (from == to) {
3865         return;
3866     }
3867 
3868     doc()->joinLines(from, to);
3869 }
3870 
reformatLines(unsigned int from,unsigned int to) const3871 void NormalViMode::reformatLines(unsigned int from, unsigned int to) const
3872 {
3873     // BUG #340550: Do not remove empty lines when reformatting
3874     KTextEditor::DocumentPrivate *document = doc();
3875     auto isNonEmptyLine = [this](QStringView text) {
3876         for (int i = 0; i < text.length(); ++i) {
3877             if (!text.at(i).isSpace()) {
3878                 return true;
3879             }
3880         }
3881 
3882         return false;
3883     };
3884     for (; from < to; ++from) {
3885         if (isNonEmptyLine(document->line(from))) {
3886             break;
3887         }
3888     }
3889     for (; to > from; --to) {
3890         if (isNonEmptyLine(document->line(to))) {
3891             break;
3892         }
3893     }
3894 
3895     document->editStart();
3896     joinLines(from, to);
3897     document->wrapText(from, to);
3898     document->editEnd();
3899 }
3900 
getFirstNonBlank(int line) const3901 int NormalViMode::getFirstNonBlank(int line) const
3902 {
3903     if (line < 0) {
3904         line = m_view->cursorPosition().line();
3905     }
3906 
3907     // doc()->plainKateTextLine returns NULL if the line is out of bounds.
3908     Kate::TextLine l = doc()->plainKateTextLine(line);
3909     Q_ASSERT(l);
3910 
3911     int c = l->firstChar();
3912     return (c < 0) ? 0 : c;
3913 }
3914 
3915 // Tries to shrinks toShrink so that it fits tightly around rangeToShrinkTo.
shrinkRangeAroundCursor(Range & toShrink,const Range & rangeToShrinkTo) const3916 void NormalViMode::shrinkRangeAroundCursor(Range &toShrink, const Range &rangeToShrinkTo) const
3917 {
3918     if (!toShrink.valid || !rangeToShrinkTo.valid) {
3919         return;
3920     }
3921     KTextEditor::Cursor cursorPos = m_view->cursorPosition();
3922     if (rangeToShrinkTo.startLine >= cursorPos.line()) {
3923         if (rangeToShrinkTo.startLine > cursorPos.line()) {
3924             // Does not surround cursor; aborting.
3925             return;
3926         }
3927         Q_ASSERT(rangeToShrinkTo.startLine == cursorPos.line());
3928         if (rangeToShrinkTo.startColumn > cursorPos.column()) {
3929             // Does not surround cursor; aborting.
3930             return;
3931         }
3932     }
3933     if (rangeToShrinkTo.endLine <= cursorPos.line()) {
3934         if (rangeToShrinkTo.endLine < cursorPos.line()) {
3935             // Does not surround cursor; aborting.
3936             return;
3937         }
3938         Q_ASSERT(rangeToShrinkTo.endLine == cursorPos.line());
3939         if (rangeToShrinkTo.endColumn < cursorPos.column()) {
3940             // Does not surround cursor; aborting.
3941             return;
3942         }
3943     }
3944 
3945     if (toShrink.startLine <= rangeToShrinkTo.startLine) {
3946         if (toShrink.startLine < rangeToShrinkTo.startLine) {
3947             toShrink.startLine = rangeToShrinkTo.startLine;
3948             toShrink.startColumn = rangeToShrinkTo.startColumn;
3949         }
3950         Q_ASSERT(toShrink.startLine == rangeToShrinkTo.startLine);
3951         if (toShrink.startColumn < rangeToShrinkTo.startColumn) {
3952             toShrink.startColumn = rangeToShrinkTo.startColumn;
3953         }
3954     }
3955     if (toShrink.endLine >= rangeToShrinkTo.endLine) {
3956         if (toShrink.endLine > rangeToShrinkTo.endLine) {
3957             toShrink.endLine = rangeToShrinkTo.endLine;
3958             toShrink.endColumn = rangeToShrinkTo.endColumn;
3959         }
3960         Q_ASSERT(toShrink.endLine == rangeToShrinkTo.endLine);
3961         if (toShrink.endColumn > rangeToShrinkTo.endColumn) {
3962             toShrink.endColumn = rangeToShrinkTo.endColumn;
3963         }
3964     }
3965 }
3966 
textObjectComma(bool inner) const3967 Range NormalViMode::textObjectComma(bool inner) const
3968 {
3969     // Basic algorithm: look left and right of the cursor for all combinations
3970     // of enclosing commas and the various types of brackets, and pick the pair
3971     // closest to the cursor that surrounds the cursor.
3972     Range r(0, 0, m_view->doc()->lines(), m_view->doc()->line(m_view->doc()->lastLine()).length(), InclusiveMotion);
3973 
3974     shrinkRangeAroundCursor(r, findSurroundingQuotes(QLatin1Char(','), inner));
3975     shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('('), QLatin1Char(')'), inner, QLatin1Char('('), QLatin1Char(')')));
3976     shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('{'), QLatin1Char('}'), inner, QLatin1Char('{'), QLatin1Char('}')));
3977     shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char(','), QLatin1Char(')'), inner, QLatin1Char('('), QLatin1Char(')')));
3978     shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char(','), QLatin1Char(']'), inner, QLatin1Char('['), QLatin1Char(']')));
3979     shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char(','), QLatin1Char('}'), inner, QLatin1Char('{'), QLatin1Char('}')));
3980     shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('('), QLatin1Char(','), inner, QLatin1Char('('), QLatin1Char(')')));
3981     shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('['), QLatin1Char(','), inner, QLatin1Char('['), QLatin1Char(']')));
3982     shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('{'), QLatin1Char(','), inner, QLatin1Char('{'), QLatin1Char('}')));
3983     return r;
3984 }
3985 
updateYankHighlightAttrib()3986 void NormalViMode::updateYankHighlightAttrib()
3987 {
3988     if (!m_highlightYankAttribute) {
3989         m_highlightYankAttribute = new KTextEditor::Attribute;
3990     }
3991     const QColor &yankedColor = m_view->renderer()->config()->savedLineColor();
3992     m_highlightYankAttribute->setBackground(yankedColor);
3993     KTextEditor::Attribute::Ptr mouseInAttribute(new KTextEditor::Attribute());
3994     mouseInAttribute->setFontBold(true);
3995     m_highlightYankAttribute->setDynamicAttribute(KTextEditor::Attribute::ActivateMouseIn, mouseInAttribute);
3996     m_highlightYankAttribute->dynamicAttribute(KTextEditor::Attribute::ActivateMouseIn)->setBackground(yankedColor);
3997 }
3998 
highlightYank(const Range & range,const OperationMode mode)3999 void NormalViMode::highlightYank(const Range &range, const OperationMode mode)
4000 {
4001     clearYankHighlight();
4002 
4003     // current MovingRange doesn't support block mode selection so split the
4004     // block range into per-line ranges
4005     if (mode == Block) {
4006         for (int i = range.startLine; i <= range.endLine; i++) {
4007             addHighlightYank(KTextEditor::Range(i, range.startColumn, i, range.endColumn));
4008         }
4009     } else {
4010         addHighlightYank(KTextEditor::Range(range.startLine, range.startColumn, range.endLine, range.endColumn));
4011     }
4012 }
4013 
addHighlightYank(const KTextEditor::Range & yankRange)4014 void NormalViMode::addHighlightYank(const KTextEditor::Range &yankRange)
4015 {
4016     KTextEditor::MovingRange *highlightedYank = m_view->doc()->newMovingRange(yankRange, Kate::TextRange::DoNotExpand);
4017     highlightedYank->setView(m_view); // show only in this view
4018     highlightedYank->setAttributeOnlyForViews(true);
4019     // use z depth defined in moving ranges interface
4020     highlightedYank->setZDepth(-10000.0);
4021     highlightedYank->setAttribute(m_highlightYankAttribute);
4022 
4023     highlightedYankForDocument().insert(highlightedYank);
4024 }
4025 
clearYankHighlight()4026 void NormalViMode::clearYankHighlight()
4027 {
4028     QSet<KTextEditor::MovingRange *> &pHighlightedYanks = highlightedYankForDocument();
4029     qDeleteAll(pHighlightedYanks);
4030     pHighlightedYanks.clear();
4031 }
4032 
aboutToDeleteMovingInterfaceContent()4033 void NormalViMode::aboutToDeleteMovingInterfaceContent()
4034 {
4035     QSet<KTextEditor::MovingRange *> &pHighlightedYanks = highlightedYankForDocument();
4036     // Prevent double-deletion in case this NormalMode is deleted.
4037     pHighlightedYanks.clear();
4038 }
4039 
highlightedYankForDocument()4040 QSet<KTextEditor::MovingRange *> &NormalViMode::highlightedYankForDocument()
4041 {
4042     // Work around the fact that both Normal and Visual mode will have their own m_highlightedYank -
4043     // make Normal's the canonical one.
4044     return m_viInputModeManager->getViNormalMode()->m_highlightedYanks;
4045 }
4046 
waitingForRegisterOrCharToSearch()4047 bool NormalViMode::waitingForRegisterOrCharToSearch()
4048 {
4049     // r, q, @ are never preceded by operators. There will always be a keys size of 1 for them.
4050     // f, t, F, T can be preceded by a delete/replace/yank/indent operator. size = 2 in that case.
4051     // f, t, F, T can be preceded by 'g' case/formatting operators. size = 3 in that case.
4052     const int keysSize = m_keys.size();
4053     if (keysSize < 1) {
4054         // Just being defensive there.
4055         return false;
4056     }
4057     if (keysSize > 1) {
4058         // Multi-letter operation.
4059         QChar cPrefix = m_keys[0];
4060         if (keysSize == 2) {
4061             // delete/replace/yank/indent operator?
4062             if (cPrefix != QLatin1Char('c') && cPrefix != QLatin1Char('d') && cPrefix != QLatin1Char('y') && cPrefix != QLatin1Char('=')
4063                 && cPrefix != QLatin1Char('>') && cPrefix != QLatin1Char('<')) {
4064                 return false;
4065             }
4066         } else if (keysSize == 3) {
4067             // We need to look deeper. Is it a g motion?
4068             QChar cNextfix = m_keys[1];
4069             if (cPrefix != QLatin1Char('g')
4070                 || (cNextfix != QLatin1Char('U') && cNextfix != QLatin1Char('u') && cNextfix != QLatin1Char('~') && cNextfix != QLatin1Char('q')
4071                     && cNextfix != QLatin1Char('w') && cNextfix != QLatin1Char('@'))) {
4072                 return false;
4073             }
4074         } else {
4075             return false;
4076         }
4077     }
4078 
4079     QChar ch = m_keys[keysSize - 1];
4080     return (ch == QLatin1Char('f') || ch == QLatin1Char('t') || ch == QLatin1Char('F')
4081             || ch == QLatin1Char('T')
4082             // c/d prefix unapplicable for the following cases.
4083             || (keysSize == 1 && (ch == QLatin1Char('r') || ch == QLatin1Char('q') || ch == QLatin1Char('@'))));
4084 }
4085 
textInserted(KTextEditor::Document * document,KTextEditor::Range range)4086 void NormalViMode::textInserted(KTextEditor::Document *document, KTextEditor::Range range)
4087 {
4088     if (m_viInputModeManager->view()->viewInputMode() != KTextEditor::View::ViInputMode) {
4089         return;
4090     }
4091 
4092     Q_UNUSED(document)
4093     const bool isInsertReplaceMode =
4094         (m_viInputModeManager->getCurrentViMode() == ViMode::InsertMode || m_viInputModeManager->getCurrentViMode() == ViMode::ReplaceMode);
4095     const bool continuesInsertion = range.start().line() == m_currentChangeEndMarker.line() && range.start().column() == m_currentChangeEndMarker.column();
4096     const bool beginsWithNewline = doc()->text(range).at(0) == QLatin1Char('\n');
4097     if (!continuesInsertion) {
4098         KTextEditor::Cursor newBeginMarkerPos = range.start();
4099         if (beginsWithNewline && !isInsertReplaceMode) {
4100             // Presumably a linewise paste, in which case we ignore the leading '\n'
4101             newBeginMarkerPos = KTextEditor::Cursor(newBeginMarkerPos.line() + 1, 0);
4102         }
4103         m_viInputModeManager->marks()->setStartEditYanked(newBeginMarkerPos);
4104     }
4105     m_viInputModeManager->marks()->setLastChange(range.start());
4106     KTextEditor::Cursor editEndMarker = range.end();
4107     if (!isInsertReplaceMode) {
4108         editEndMarker.setColumn(editEndMarker.column() - 1);
4109     }
4110     m_viInputModeManager->marks()->setFinishEditYanked(editEndMarker);
4111     m_currentChangeEndMarker = range.end();
4112     if (m_isUndo) {
4113         const bool addsMultipleLines = range.start().line() != range.end().line();
4114         m_viInputModeManager->marks()->setStartEditYanked(KTextEditor::Cursor(m_viInputModeManager->marks()->getStartEditYanked().line(), 0));
4115         if (addsMultipleLines) {
4116             m_viInputModeManager->marks()->setFinishEditYanked(KTextEditor::Cursor(m_viInputModeManager->marks()->getFinishEditYanked().line() + 1, 0));
4117             m_viInputModeManager->marks()->setLastChange(KTextEditor::Cursor(m_viInputModeManager->marks()->getLastChange().line() + 1, 0));
4118         } else {
4119             m_viInputModeManager->marks()->setFinishEditYanked(KTextEditor::Cursor(m_viInputModeManager->marks()->getFinishEditYanked().line(), 0));
4120             m_viInputModeManager->marks()->setLastChange(KTextEditor::Cursor(m_viInputModeManager->marks()->getLastChange().line(), 0));
4121         }
4122     }
4123 }
4124 
textRemoved(KTextEditor::Document * document,KTextEditor::Range range)4125 void NormalViMode::textRemoved(KTextEditor::Document *document, KTextEditor::Range range)
4126 {
4127     if (m_viInputModeManager->view()->viewInputMode() != KTextEditor::View::ViInputMode) {
4128         return;
4129     }
4130 
4131     Q_UNUSED(document);
4132     const bool isInsertReplaceMode =
4133         (m_viInputModeManager->getCurrentViMode() == ViMode::InsertMode || m_viInputModeManager->getCurrentViMode() == ViMode::ReplaceMode);
4134     m_viInputModeManager->marks()->setLastChange(range.start());
4135     if (!isInsertReplaceMode) {
4136         // Don't go resetting [ just because we did a Ctrl-h!
4137         m_viInputModeManager->marks()->setStartEditYanked(range.start());
4138     } else {
4139         // Don't go disrupting our continued insertion just because we did a Ctrl-h!
4140         m_currentChangeEndMarker = range.start();
4141     }
4142     m_viInputModeManager->marks()->setFinishEditYanked(range.start());
4143     if (m_isUndo) {
4144         // Slavishly follow Vim's weird rules: if an undo removes several lines, then all markers should
4145         // be at the beginning of the line after the last line removed, else they should at the beginning
4146         // of the line above that.
4147         const int markerLineAdjustment = (range.start().line() != range.end().line()) ? 1 : 0;
4148         m_viInputModeManager->marks()->setStartEditYanked(
4149             KTextEditor::Cursor(m_viInputModeManager->marks()->getStartEditYanked().line() + markerLineAdjustment, 0));
4150         m_viInputModeManager->marks()->setFinishEditYanked(
4151             KTextEditor::Cursor(m_viInputModeManager->marks()->getFinishEditYanked().line() + markerLineAdjustment, 0));
4152         m_viInputModeManager->marks()->setLastChange(KTextEditor::Cursor(m_viInputModeManager->marks()->getLastChange().line() + markerLineAdjustment, 0));
4153     }
4154 }
4155 
undoBeginning()4156 void NormalViMode::undoBeginning()
4157 {
4158     m_isUndo = true;
4159 }
4160 
undoEnded()4161 void NormalViMode::undoEnded()
4162 {
4163     m_isUndo = false;
4164 }
4165 
executeKateCommand(const QString & command)4166 bool NormalViMode::executeKateCommand(const QString &command)
4167 {
4168     KTextEditor::Command *p = KateCmd::self()->queryCommand(command);
4169 
4170     if (!p) {
4171         return false;
4172     }
4173 
4174     QString msg;
4175     return p->exec(m_view, command, msg);
4176 }
4177