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

source code of ktexteditor/src/vimode/modes/normalvimode.cpp