KTextEditor

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

KDE's Doxygen guidelines are available online.