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

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