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::commandChange()
1066{
1067 KTextEditor::Cursor c(m_view->cursorPosition());
1068
1069 OperationMode m = getOperationMode();
1070
1071 doc()->setUndoMergeAllEdits(true);
1072
1073 commandDelete();
1074
1075 if (m == LineWise) {
1076 // if we deleted several lines, insert an empty line and put the cursor there.
1077 doc()->insertLine(m_commandRange.startLine, QString());
1078 c.setLine(m_commandRange.startLine);
1079 c.setColumn(0);
1080 } else if (m == Block) {
1081 // block substitute can be simulated by first deleting the text
1082 // (done above) and then starting block prepend.
1083 return commandPrependToBlock();
1084 } else {
1085 if (m_commandRange.startLine < m_commandRange.endLine) {
1086 c.setLine(m_commandRange.startLine);
1087 }
1088 c.setColumn(m_commandRange.startColumn);
1089 }
1090
1091 updateCursor(c);
1092 setCount(0); // The count was for the motion, not the insertion.
1094
1095 // correct indentation level
1096 if (m == LineWise) {
1097 m_view->align();
1098 }
1099
1100 m_deleteCommand = true;
1101 return true;
1102}
1103
1104bool NormalViMode::commandChangeToEOL()
1105{
1106 commandDeleteToEOL();
1107
1108 if (getOperationMode() == Block) {
1109 return commandPrependToBlock();
1110 }
1111
1112 m_deleteCommand = true;
1114}
1115
1116bool NormalViMode::commandChangeLine()
1117{
1118 m_deleteCommand = true;
1119 KTextEditor::Cursor c(m_view->cursorPosition());
1120 c.setColumn(0);
1121 updateCursor(c);
1122
1123 doc()->setUndoMergeAllEdits(true);
1124
1125 // if count >= 2 start by deleting the whole lines
1126 if (getCount() >= 2) {
1127 Range r(c.line(), 0, c.line() + getCount() - 2, 0, InclusiveMotion);
1128 deleteRange(r);
1129 }
1130
1131 // ... then delete the _contents_ of the last line, but keep the line
1132 Range r(c.line(), c.column(), c.line(), doc()->lineLength(c.line()) - 1, InclusiveMotion);
1133 deleteRange(r, CharWise, true);
1134
1135 // ... then enter insert mode
1136 if (getOperationMode() == Block) {
1137 return commandPrependToBlock();
1138 }
1140
1141 // correct indentation level
1142 m_view->align();
1143
1144 return true;
1145}
1146
1147bool NormalViMode::commandSubstituteChar()
1148{
1149 if (commandDeleteChar()) {
1150 // The count is only used for deletion of chars; the inserted text is not repeated
1151 setCount(0);
1152 return commandEnterInsertMode();
1153 }
1154
1155 m_deleteCommand = true;
1156 return false;
1157}
1158
1159bool NormalViMode::commandSubstituteLine()
1160{
1161 m_deleteCommand = true;
1162 return commandChangeLine();
1163}
1164
1165bool NormalViMode::commandYank()
1166{
1167 bool r = false;
1168 QString yankedText;
1169
1170 OperationMode m = getOperationMode();
1171 yankedText = getRange(m_commandRange, m);
1172
1173 highlightYank(m_commandRange, m);
1174
1175 QChar chosen_register = getChosenRegister(ZeroRegister);
1176 fillRegister(chosen_register, yankedText, m);
1177 yankToClipBoard(chosen_register, yankedText);
1178
1179 return r;
1180}
1181
1182bool NormalViMode::commandYankLine()
1183{
1184 KTextEditor::Cursor c(m_view->cursorPosition());
1185 QString lines;
1186 int linenum = c.line();
1187
1188 for (int i = 0; i < getCount(); i++) {
1189 lines.append(getLine(linenum + i) + QLatin1Char('\n'));
1190 }
1191
1192 Range yankRange(linenum, 0, linenum + getCount() - 1, getLine(linenum + getCount() - 1).length(), InclusiveMotion);
1193 highlightYank(yankRange);
1194
1195 QChar chosen_register = getChosenRegister(ZeroRegister);
1196 fillRegister(chosen_register, lines, LineWise);
1197 yankToClipBoard(chosen_register, lines);
1198
1199 return true;
1200}
1201
1202bool NormalViMode::commandYankToEOL()
1203{
1204 OperationMode m = CharWise;
1205 KTextEditor::Cursor c(m_view->cursorPosition());
1206
1207 MotionType motion = m_commandRange.motionType;
1208 m_commandRange.endLine = c.line() + getCount() - 1;
1209 m_commandRange.endColumn = doc()->lineLength(m_commandRange.endLine) - 1;
1210 m_commandRange.motionType = InclusiveMotion;
1211
1212 switch (m_viInputModeManager->getCurrentViMode()) {
1213 case ViMode::NormalMode:
1214 m_commandRange.startLine = c.line();
1215 m_commandRange.startColumn = c.column();
1216 break;
1217 case ViMode::VisualMode:
1218 case ViMode::VisualLineMode:
1219 m = LineWise;
1220 {
1221 VisualViMode *visual = static_cast<VisualViMode *>(this);
1222 visual->setStart(KTextEditor::Cursor(visual->getStart().line(), 0));
1223 }
1224 break;
1225 case ViMode::VisualBlockMode:
1226 m = Block;
1227 break;
1228 default:
1229 /* InsertMode and ReplaceMode will never call this method. */
1230 Q_ASSERT(false);
1231 }
1232
1233 const QString &yankedText = getRange(m_commandRange, m);
1234 m_commandRange.motionType = motion;
1235 highlightYank(m_commandRange);
1236
1237 QChar chosen_register = getChosenRegister(ZeroRegister);
1238 fillRegister(chosen_register, yankedText, m);
1239 yankToClipBoard(chosen_register, yankedText);
1240
1241 return true;
1242}
1243
1244// Insert the text in the given register after the cursor position.
1245// This is the non-g version of paste, so the cursor will usually
1246// end up on the last character of the pasted text, unless the text
1247// was multi-line or linewise in which case it will end up
1248// on the *first* character of the pasted text(!)
1249// If linewise, will paste after the current line.
1250bool NormalViMode::commandPaste()
1251{
1252 return paste(AfterCurrentPosition, false, false);
1253}
1254
1255// As with commandPaste, except that the text is pasted *at* the cursor position
1256bool NormalViMode::commandPasteBefore()
1257{
1258 return paste(AtCurrentPosition, false, false);
1259}
1260
1261// As with commandPaste, except that the cursor will generally be placed *after* the
1262// last pasted character (assuming the last pasted character is not at the end of the line).
1263// If linewise, cursor will be at the beginning of the line *after* the last line of pasted text,
1264// unless that line is the last line of the document; then it will be placed at the beginning of the
1265// last line pasted.
1266bool NormalViMode::commandgPaste()
1267{
1268 return paste(AfterCurrentPosition, true, false);
1269}
1270
1271// As with commandgPaste, except that it pastes *at* the current cursor position or, if linewise,
1272// at the current line.
1273bool NormalViMode::commandgPasteBefore()
1274{
1275 return paste(AtCurrentPosition, true, false);
1276}
1277
1278bool NormalViMode::commandIndentedPaste()
1279{
1280 return paste(AfterCurrentPosition, false, true);
1281}
1282
1283bool NormalViMode::commandIndentedPasteBefore()
1284{
1285 return paste(AtCurrentPosition, false, true);
1286}
1287
1288bool NormalViMode::commandDeleteChar()
1289{
1290 KTextEditor::Cursor c(m_view->cursorPosition());
1291 Range r(c.line(), c.column(), c.line(), c.column() + getCount(), ExclusiveMotion);
1292
1293 if (m_commandRange.startLine != -1 && m_commandRange.startColumn != -1) {
1294 r = m_commandRange;
1295 } else {
1296 if (r.endColumn > doc()->lineLength(r.startLine)) {
1297 r.endColumn = doc()->lineLength(r.startLine);
1298 }
1299 }
1300
1301 // should delete entire lines if in visual line mode and selection in visual block mode
1302 OperationMode m = CharWise;
1303 if (m_viInputModeManager->getCurrentViMode() == VisualLineMode) {
1304 m = LineWise;
1305 } else if (m_viInputModeManager->getCurrentViMode() == VisualBlockMode) {
1306 m = Block;
1307 }
1308
1309 m_deleteCommand = true;
1310 return deleteRange(r, m);
1311}
1312
1313bool NormalViMode::commandDeleteCharBackward()
1314{
1315 KTextEditor::Cursor c(m_view->cursorPosition());
1316
1317 Range r(c.line(), c.column() - getCount(), c.line(), c.column(), ExclusiveMotion);
1318
1319 if (m_commandRange.startLine != -1 && m_commandRange.startColumn != -1) {
1320 r = m_commandRange;
1321 } else {
1322 if (r.startColumn < 0) {
1323 r.startColumn = 0;
1324 }
1325 }
1326
1327 // should delete entire lines if in visual line mode and selection in visual block mode
1328 OperationMode m = CharWise;
1329 if (m_viInputModeManager->getCurrentViMode() == VisualLineMode) {
1330 m = LineWise;
1331 } else if (m_viInputModeManager->getCurrentViMode() == VisualBlockMode) {
1332 m = Block;
1333 }
1334
1335 m_deleteCommand = true;
1336 return deleteRange(r, m);
1337}
1338
1339bool NormalViMode::commandReplaceCharacter()
1340{
1341 QString key = KeyParser::self()->decodeKeySequence(m_keys.right(1));
1342
1343 // Filter out some special keys.
1344 const int keyCode = KeyParser::self()->encoded2qt(m_keys.right(1));
1345 switch (keyCode) {
1346 case Qt::Key_Left:
1347 case Qt::Key_Right:
1348 case Qt::Key_Up:
1349 case Qt::Key_Down:
1350 case Qt::Key_Home:
1351 case Qt::Key_End:
1352 case Qt::Key_PageUp:
1353 case Qt::Key_PageDown:
1354 case Qt::Key_Delete:
1355 case Qt::Key_Insert:
1356 case Qt::Key_Backspace:
1357 case Qt::Key_CapsLock:
1358 return true;
1359 case Qt::Key_Return:
1360 case Qt::Key_Enter:
1361 key = QStringLiteral("\n");
1362 }
1363
1364 bool r;
1365 if (m_viInputModeManager->isAnyVisualMode()) {
1366 OperationMode m = getOperationMode();
1367 QString text = getRange(m_commandRange, m);
1368
1369 if (m == LineWise) {
1370 text.chop(1); // don't need '\n' at the end;
1371 }
1372
1373 static const QRegularExpression nonNewlineRegex(QStringLiteral("[^\n]"));
1374 text.replace(nonNewlineRegex, key);
1375
1376 m_commandRange.normalize();
1377 KTextEditor::Cursor start(m_commandRange.startLine, m_commandRange.startColumn);
1378 KTextEditor::Cursor end(m_commandRange.endLine, m_commandRange.endColumn);
1379 KTextEditor::Range range(start, end);
1380
1381 r = doc()->replaceText(range, text, m == Block);
1382
1383 } else {
1384 KTextEditor::Cursor c1(m_view->cursorPosition());
1385 KTextEditor::Cursor c2(m_view->cursorPosition());
1386
1387 c2.setColumn(c2.column() + getCount());
1388
1389 if (c2.column() > doc()->lineLength(m_view->cursorPosition().line())) {
1390 return false;
1391 }
1392
1393 r = doc()->replaceText(KTextEditor::Range(c1, c2), key.repeated(getCount()));
1394 updateCursor(c1);
1395 }
1396 return r;
1397}
1398
1399bool NormalViMode::commandSwitchToCmdLine()
1400{
1401 QString initialText;
1402 if (m_viInputModeManager->isAnyVisualMode()) {
1403 // if in visual mode, make command range == visual selection
1404 m_viInputModeManager->getViVisualMode()->saveRangeMarks();
1405 initialText = QStringLiteral("'<,'>");
1406 } else if (getCount() != 1) {
1407 // if a count is given, the range [current line] to [current line] +
1408 // count should be prepended to the command line
1409 initialText = QLatin1String(".,.+") + QString::number(getCount() - 1);
1410 }
1411
1412 m_viInputModeManager->inputAdapter()->showViModeEmulatedCommandBar();
1413 m_viInputModeManager->inputAdapter()->viModeEmulatedCommandBar()->init(EmulatedCommandBar::Command, initialText);
1414
1415 m_commandShouldKeepSelection = true;
1416
1417 return true;
1418}
1419
1420bool NormalViMode::commandSearchBackward()
1421{
1422 m_viInputModeManager->inputAdapter()->showViModeEmulatedCommandBar();
1423 m_viInputModeManager->inputAdapter()->viModeEmulatedCommandBar()->init(EmulatedCommandBar::SearchBackward);
1424 return true;
1425}
1426
1427bool NormalViMode::commandSearchForward()
1428{
1429 m_viInputModeManager->inputAdapter()->showViModeEmulatedCommandBar();
1430 m_viInputModeManager->inputAdapter()->viModeEmulatedCommandBar()->init(EmulatedCommandBar::SearchForward);
1431 return true;
1432}
1433
1434bool NormalViMode::commandUndo()
1435{
1436 // See BUG #328277
1437 m_viInputModeManager->clearCurrentChangeLog();
1438
1439 if (doc()->undoCount() > 0) {
1440 const bool mapped = m_viInputModeManager->keyMapper()->isExecutingMapping();
1441
1442 if (mapped) {
1443 doc()->editEnd();
1444 }
1445 doc()->undo();
1446 if (mapped) {
1447 doc()->editStart();
1448 }
1449 if (m_viInputModeManager->isAnyVisualMode()) {
1450 m_viInputModeManager->getViVisualMode()->setStart(KTextEditor::Cursor(-1, -1));
1451 m_view->clearSelection();
1452 startNormalMode();
1453 }
1454 return true;
1455 }
1456 return false;
1457}
1458
1459bool NormalViMode::commandRedo()
1460{
1461 if (doc()->redoCount() > 0) {
1462 const bool mapped = m_viInputModeManager->keyMapper()->isExecutingMapping();
1463
1464 if (mapped) {
1465 doc()->editEnd();
1466 }
1467 doc()->redo();
1468 if (mapped) {
1469 doc()->editStart();
1470 }
1471 if (m_viInputModeManager->isAnyVisualMode()) {
1472 m_viInputModeManager->getViVisualMode()->setStart(KTextEditor::Cursor(-1, -1));
1473 m_view->clearSelection();
1474 startNormalMode();
1475 }
1476 return true;
1477 }
1478 return false;
1479}
1480
1481bool NormalViMode::commandSetMark()
1482{
1483 KTextEditor::Cursor c(m_view->cursorPosition());
1484
1485 QChar mark = m_keys.at(m_keys.size() - 1);
1486 m_viInputModeManager->marks()->setUserMark(mark, c);
1487
1488 return true;
1489}
1490
1491bool NormalViMode::commandIndentLine()
1492{
1493 KTextEditor::Cursor c(m_view->cursorPosition());
1494
1495 doc()->indent(KTextEditor::Range(c.line(), 0, c.line() + getCount(), 0), 1);
1496
1497 return true;
1498}
1499
1500bool NormalViMode::commandUnindentLine()
1501{
1502 KTextEditor::Cursor c(m_view->cursorPosition());
1503
1504 doc()->indent(KTextEditor::Range(c.line(), 0, c.line() + getCount(), 0), -1);
1505
1506 return true;
1507}
1508
1509bool NormalViMode::commandIndentLines()
1510{
1511 const bool downwards = m_commandRange.startLine < m_commandRange.endLine;
1512
1513 m_commandRange.normalize();
1514
1515 int line1 = m_commandRange.startLine;
1516 int line2 = m_commandRange.endLine;
1517 int col = getLine(line2).length();
1518 doc()->indent(KTextEditor::Range(line1, 0, line2, col), getCount());
1519
1520 if (downwards) {
1521 updateCursor(KTextEditor::Cursor(m_commandRange.startLine, m_commandRange.startColumn));
1522 } else {
1523 updateCursor(KTextEditor::Cursor(m_commandRange.endLine, m_commandRange.endColumn));
1524 }
1525 return true;
1526}
1527
1528bool NormalViMode::commandUnindentLines()
1529{
1530 const bool downwards = m_commandRange.startLine < m_commandRange.endLine;
1531
1532 m_commandRange.normalize();
1533
1534 int line1 = m_commandRange.startLine;
1535 int line2 = m_commandRange.endLine;
1536
1537 doc()->indent(KTextEditor::Range(line1, 0, line2, doc()->lineLength(line2)), -getCount());
1538
1539 if (downwards) {
1540 updateCursor(KTextEditor::Cursor(m_commandRange.startLine, m_commandRange.startColumn));
1541 } else {
1542 updateCursor(KTextEditor::Cursor(m_commandRange.endLine, m_commandRange.endColumn));
1543 }
1544 return true;
1545}
1546
1547bool NormalViMode::commandScrollPageDown()
1548{
1549 if (getCount() < m_scroll_count_limit) {
1550 for (int i = 0; i < getCount(); i++) {
1551 m_view->pageDown();
1552 }
1553 }
1554 return true;
1555}
1556
1557bool NormalViMode::commandScrollPageUp()
1558{
1559 if (getCount() < m_scroll_count_limit) {
1560 for (int i = 0; i < getCount(); i++) {
1561 m_view->pageUp();
1562 }
1563 }
1564 return true;
1565}
1566
1567bool NormalViMode::commandScrollHalfPageUp()
1568{
1569 if (getCount() < m_scroll_count_limit) {
1570 for (int i = 0; i < getCount(); i++) {
1571 m_viewInternal->pageUp(false, true);
1572 }
1573 }
1574 return true;
1575}
1576
1577bool NormalViMode::commandScrollHalfPageDown()
1578{
1579 if (getCount() < m_scroll_count_limit) {
1580 for (int i = 0; i < getCount(); i++) {
1581 m_viewInternal->pageDown(false, true);
1582 }
1583 }
1584 return true;
1585}
1586
1587bool NormalViMode::commandCenterView(bool onFirst)
1588{
1589 KTextEditor::Cursor c(m_view->cursorPosition());
1590 const int virtualCenterLine = m_viewInternal->startLine() + linesDisplayed() / 2;
1591 const int virtualCursorLine = m_view->textFolding().lineToVisibleLine(c.line());
1592
1593 scrollViewLines(virtualCursorLine - virtualCenterLine);
1594 if (onFirst) {
1596 updateCursor(c);
1597 }
1598 return true;
1599}
1600
1601bool NormalViMode::commandCenterViewOnNonBlank()
1602{
1603 return commandCenterView(true);
1604}
1605
1606bool NormalViMode::commandCenterViewOnCursor()
1607{
1608 return commandCenterView(false);
1609}
1610
1611bool NormalViMode::commandTopView(bool onFirst)
1612{
1613 KTextEditor::Cursor c(m_view->cursorPosition());
1614 const int virtualCenterLine = m_viewInternal->startLine();
1615 const int virtualCursorLine = m_view->textFolding().lineToVisibleLine(c.line());
1616
1617 scrollViewLines(virtualCursorLine - virtualCenterLine);
1618 if (onFirst) {
1620 updateCursor(c);
1621 }
1622 return true;
1623}
1624
1625bool NormalViMode::commandTopViewOnNonBlank()
1626{
1627 return commandTopView(true);
1628}
1629
1630bool NormalViMode::commandTopViewOnCursor()
1631{
1632 return commandTopView(false);
1633}
1634
1635bool NormalViMode::commandBottomView(bool onFirst)
1636{
1637 KTextEditor::Cursor c(m_view->cursorPosition());
1638 const int virtualCenterLine = m_viewInternal->endLine();
1639 const int virtualCursorLine = m_view->textFolding().lineToVisibleLine(c.line());
1640
1641 scrollViewLines(virtualCursorLine - virtualCenterLine);
1642 if (onFirst) {
1644 updateCursor(c);
1645 }
1646 return true;
1647}
1648
1649bool NormalViMode::commandBottomViewOnNonBlank()
1650{
1651 return commandBottomView(true);
1652}
1653
1654bool NormalViMode::commandBottomViewOnCursor()
1655{
1656 return commandBottomView(false);
1657}
1658
1659bool NormalViMode::commandAbort()
1660{
1661 m_pendingResetIsDueToExit = true;
1662 reset();
1663 return true;
1664}
1665
1666bool NormalViMode::commandPrintCharacterCode()
1667{
1668 QChar ch = getCharUnderCursor();
1669
1670 if (ch == QChar::Null) {
1671 message(QStringLiteral("NUL"));
1672 } else {
1673 int code = ch.unicode();
1674
1675 QString dec = QString::number(code);
1676 QString hex = QString::number(code, 16);
1677 QString oct = QString::number(code, 8);
1678 if (oct.length() < 3) {
1679 oct.prepend(QLatin1Char('0'));
1680 }
1681 if (code > 0x80 && code < 0x1000) {
1682 hex.prepend((code < 0x100 ? QLatin1String("00") : QLatin1String("0")));
1683 }
1684 message(i18n("'%1' %2, Hex %3, Octal %4", ch, dec, hex, oct));
1685 }
1686
1687 return true;
1688}
1689
1690bool NormalViMode::commandRepeatLastChange()
1691{
1692 const int repeatCount = getCount();
1693 resetParser();
1694 if (repeatCount > 1) {
1695 m_oneTimeCountOverride = repeatCount;
1696 }
1697 doc()->editStart();
1698 m_viInputModeManager->repeatLastChange();
1699 doc()->editEnd();
1700
1701 return true;
1702}
1703
1704bool NormalViMode::commandAlignLine()
1705{
1706 const int line = m_view->cursorPosition().line();
1707 KTextEditor::Range alignRange(KTextEditor::Cursor(line, 0), KTextEditor::Cursor(line, 0));
1708
1709 doc()->align(m_view, alignRange);
1710
1711 return true;
1712}
1713
1714bool NormalViMode::commandAlignLines()
1715{
1716 m_commandRange.normalize();
1717
1718 KTextEditor::Cursor start(m_commandRange.startLine, 0);
1719 KTextEditor::Cursor end(m_commandRange.endLine, 0);
1720
1721 doc()->align(m_view, KTextEditor::Range(start, end));
1722
1723 return true;
1724}
1725
1726bool NormalViMode::commandAddToNumber()
1727{
1728 addToNumberUnderCursor(getCount());
1729
1730 return true;
1731}
1732
1733bool NormalViMode::commandSubtractFromNumber()
1734{
1735 addToNumberUnderCursor(-getCount());
1736
1737 return true;
1738}
1739
1740bool NormalViMode::commandPrependToBlock()
1741{
1742 KTextEditor::Cursor c(m_view->cursorPosition());
1743
1744 // move cursor to top left corner of selection
1745 m_commandRange.normalize();
1746 c.setColumn(m_commandRange.startColumn);
1747 c.setLine(m_commandRange.startLine);
1748 updateCursor(c);
1749
1750 m_stickyColumn = -1;
1751 m_viInputModeManager->getViInsertMode()->setBlockPrependMode(m_commandRange);
1752 return startInsertMode();
1753}
1754
1755bool NormalViMode::commandAppendToBlock()
1756{
1757 KTextEditor::Cursor c(m_view->cursorPosition());
1758
1759 m_commandRange.normalize();
1760 if (m_stickyColumn == (unsigned int)KateVi::EOL) { // append to EOL
1761 // move cursor to end of first line
1762 c.setLine(m_commandRange.startLine);
1763 c.setColumn(doc()->lineLength(c.line()));
1764 updateCursor(c);
1765 m_viInputModeManager->getViInsertMode()->setBlockAppendMode(m_commandRange, AppendEOL);
1766 } else {
1767 m_viInputModeManager->getViInsertMode()->setBlockAppendMode(m_commandRange, Append);
1768 // move cursor to top right corner of selection
1769 c.setColumn(m_commandRange.endColumn + 1);
1770 c.setLine(m_commandRange.startLine);
1771 updateCursor(c);
1772 }
1773
1774 m_stickyColumn = -1;
1775
1776 return startInsertMode();
1777}
1778
1779bool NormalViMode::commandGoToNextJump()
1780{
1781 KTextEditor::Cursor c = getNextJump(m_view->cursorPosition());
1782 updateCursor(c);
1783
1784 return true;
1785}
1786
1787bool NormalViMode::commandGoToPrevJump()
1788{
1789 KTextEditor::Cursor c = getPrevJump(m_view->cursorPosition());
1790 updateCursor(c);
1791
1792 return true;
1793}
1794
1795bool NormalViMode::commandSwitchToLeftView()
1796{
1797 switchView(Left);
1798 return true;
1799}
1800
1801bool NormalViMode::commandSwitchToDownView()
1802{
1803 switchView(Down);
1804 return true;
1805}
1806
1807bool NormalViMode::commandSwitchToUpView()
1808{
1809 switchView(Up);
1810 return true;
1811}
1812
1813bool NormalViMode::commandSwitchToRightView()
1814{
1815 switchView(Right);
1816 return true;
1817}
1818
1819bool NormalViMode::commandSwitchToNextView()
1820{
1821 switchView(Next);
1822 return true;
1823}
1824
1825bool NormalViMode::commandSplitHoriz()
1826{
1827 return executeKateCommand(QStringLiteral("split"));
1828}
1829
1830bool NormalViMode::commandSplitVert()
1831{
1832 return executeKateCommand(QStringLiteral("vsplit"));
1833}
1834
1835bool NormalViMode::commandCloseView()
1836{
1837 return executeKateCommand(QStringLiteral("close"));
1838}
1839
1840bool NormalViMode::commandSwitchToNextTab()
1841{
1842 QString command = QStringLiteral("bn");
1843
1844 if (m_iscounted) {
1845 command = command + QLatin1Char(' ') + QString::number(getCount());
1846 }
1847
1848 return executeKateCommand(command);
1849}
1850
1851bool NormalViMode::commandSwitchToPrevTab()
1852{
1853 QString command = QStringLiteral("bp");
1854
1855 if (m_iscounted) {
1856 command = command + QLatin1Char(' ') + QString::number(getCount());
1857 }
1858
1859 return executeKateCommand(command);
1860}
1861
1862bool NormalViMode::commandFormatLine()
1863{
1864 KTextEditor::Cursor c(m_view->cursorPosition());
1865
1866 reformatLines(c.line(), c.line() + getCount() - 1);
1867
1868 return true;
1869}
1870
1871bool NormalViMode::commandFormatLines()
1872{
1873 reformatLines(m_commandRange.startLine, m_commandRange.endLine);
1874 return true;
1875}
1876
1877bool NormalViMode::commandCollapseToplevelNodes()
1878{
1879 m_view->slotFoldToplevelNodes();
1880 return true;
1881}
1882
1883bool NormalViMode::commandStartRecordingMacro()
1884{
1885 const QChar reg = m_keys[m_keys.size() - 1];
1886 m_viInputModeManager->macroRecorder()->start(reg);
1887 return true;
1888}
1889
1890bool NormalViMode::commandReplayMacro()
1891{
1892 // "@<registername>" will have been added to the log; it needs to be cleared
1893 // *before* we replay the macro keypresses, else it can cause an infinite loop
1894 // if the macro contains a "."
1895 m_viInputModeManager->clearCurrentChangeLog();
1896 const QChar reg = m_keys[m_keys.size() - 1];
1897 const unsigned int count = getCount();
1898 resetParser();
1899 doc()->editStart();
1900 for (unsigned int i = 0; i < count; i++) {
1901 m_viInputModeManager->macroRecorder()->replay(reg);
1902 }
1903 doc()->editEnd();
1904 return true;
1905}
1906
1907bool NormalViMode::commandCloseNocheck()
1908{
1909 return executeKateCommand(QStringLiteral("q!"));
1910}
1911
1912bool NormalViMode::commandCloseWrite()
1913{
1914 return executeKateCommand(QStringLiteral("wq"));
1915}
1916
1917bool NormalViMode::commandCollapseLocal()
1918{
1919 int line = m_view->cursorPosition().line();
1920 bool actionDone = false;
1921 while (!actionDone && line > -1) {
1922 actionDone = m_view->foldLine(line--).isValid();
1923 }
1924 return true;
1925}
1926
1927bool NormalViMode::commandExpandAll()
1928{
1929 // FIXME: is toplevel same as all?
1930 m_view->slotExpandToplevelNodes();
1931 return true;
1932}
1933
1934bool NormalViMode::commandExpandLocal()
1935{
1936 int line = m_view->cursorPosition().line();
1937 return m_view->unfoldLine(line);
1938}
1939
1940bool NormalViMode::commandToggleRegionVisibility()
1941{
1942 // FIXME: is this equivalent to Vim (or a desired change)?
1943 m_view->slotToggleFolding();
1944 return true;
1945}
1946
1947////////////////////////////////////////////////////////////////////////////////
1948// MOTIONS
1949////////////////////////////////////////////////////////////////////////////////
1950
1951Range NormalViMode::motionDown()
1952{
1953 return goLineDown();
1954}
1955
1956Range NormalViMode::motionUp()
1957{
1958 return goLineUp();
1959}
1960
1961Range NormalViMode::motionLeft()
1962{
1963 KTextEditor::Cursor cursor(m_view->cursorPosition());
1964 m_stickyColumn = -1;
1965 Range r(cursor, ExclusiveMotion);
1966 r.endColumn -= getCount();
1967
1968 if (r.endColumn < 0) {
1969 r.endColumn = 0;
1970 }
1971
1972 return r;
1973}
1974
1975Range NormalViMode::motionRight()
1976{
1977 KTextEditor::Cursor cursor(m_view->cursorPosition());
1978 m_stickyColumn = -1;
1979 Range r(cursor, ExclusiveMotion);
1980 r.endColumn += getCount();
1981
1982 // make sure end position isn't > line length
1983 if (r.endColumn > doc()->lineLength(r.endLine)) {
1984 r.endColumn = doc()->lineLength(r.endLine);
1985 }
1986
1987 return r;
1988}
1989
1990Range NormalViMode::motionPageDown()
1991{
1992 KTextEditor::Cursor c(m_view->cursorPosition());
1993 Range r(c, InclusiveMotion);
1994 r.endLine += linesDisplayed();
1995
1996 if (r.endLine >= doc()->lines()) {
1997 r.endLine = doc()->lines() - 1;
1998 }
1999 return r;
2000}
2001
2002Range NormalViMode::motionPageUp()
2003{
2004 KTextEditor::Cursor c(m_view->cursorPosition());
2005 Range r(c, InclusiveMotion);
2006 r.endLine -= linesDisplayed();
2007
2008 if (r.endLine < 0) {
2009 r.endLine = 0;
2010 }
2011 return r;
2012}
2013
2014Range NormalViMode::motionHalfPageDown()
2015{
2016 if (commandScrollHalfPageDown()) {
2017 KTextEditor::Cursor c = m_view->cursorPosition();
2018 m_commandRange.endLine = c.line();
2019 m_commandRange.endColumn = c.column();
2020 return m_commandRange;
2021 }
2022 return Range::invalid();
2023}
2024
2025Range NormalViMode::motionHalfPageUp()
2026{
2027 if (commandScrollHalfPageUp()) {
2028 KTextEditor::Cursor c = m_view->cursorPosition();
2029 m_commandRange.endLine = c.line();
2030 m_commandRange.endColumn = c.column();
2031 return m_commandRange;
2032 }
2033 return Range::invalid();
2034}
2035
2036Range NormalViMode::motionDownToFirstNonBlank()
2037{
2038 Range r = goLineDown();
2039 r.endColumn = getFirstNonBlank(r.endLine);
2040 return r;
2041}
2042
2043Range NormalViMode::motionUpToFirstNonBlank()
2044{
2045 Range r = goLineUp();
2046 r.endColumn = getFirstNonBlank(r.endLine);
2047 return r;
2048}
2049
2050Range NormalViMode::motionWordForward()
2051{
2052 KTextEditor::Cursor c(m_view->cursorPosition());
2053 Range r(c, ExclusiveMotion);
2054
2055 m_stickyColumn = -1;
2056
2057 // Special case: If we're already on the very last character in the document, the motion should be
2058 // inclusive so the last character gets included
2059 if (c.line() == doc()->lines() - 1 && c.column() == doc()->lineLength(c.line()) - 1) {
2060 r.motionType = InclusiveMotion;
2061 } else {
2062 for (int i = 0; i < getCount(); i++) {
2063 c = findNextWordStart(c.line(), c.column());
2064
2065 // stop when at the last char in the document
2066 if (!c.isValid()) {
2067 c = doc()->documentEnd();
2068 // if we still haven't "used up the count", make the motion inclusive, so that the last char
2069 // is included
2070 if (i < getCount()) {
2071 r.motionType = InclusiveMotion;
2072 }
2073 break;
2074 }
2075 }
2076 }
2077
2078 r.endColumn = c.column();
2079 r.endLine = c.line();
2080
2081 return r;
2082}
2083
2084Range NormalViMode::motionWordBackward()
2085{
2086 KTextEditor::Cursor c(m_view->cursorPosition());
2087 Range r(c, ExclusiveMotion);
2088
2089 m_stickyColumn = -1;
2090
2091 for (int i = 0; i < getCount(); i++) {
2092 c = findPrevWordStart(c.line(), c.column());
2093
2094 if (!c.isValid()) {
2095 c = KTextEditor::Cursor(0, 0);
2096 break;
2097 }
2098 }
2099
2100 r.endColumn = c.column();
2101 r.endLine = c.line();
2102
2103 return r;
2104}
2105
2106Range NormalViMode::motionWORDForward()
2107{
2108 KTextEditor::Cursor c(m_view->cursorPosition());
2109 Range r(c, ExclusiveMotion);
2110
2111 m_stickyColumn = -1;
2112
2113 for (int i = 0; i < getCount(); i++) {
2114 c = findNextWORDStart(c.line(), c.column());
2115
2116 // stop when at the last char in the document
2117 if (c.line() == doc()->lines() - 1 && c.column() == doc()->lineLength(c.line()) - 1) {
2118 break;
2119 }
2120 }
2121
2122 r.endColumn = c.column();
2123 r.endLine = c.line();
2124
2125 return r;
2126}
2127
2128Range NormalViMode::motionWORDBackward()
2129{
2130 KTextEditor::Cursor c(m_view->cursorPosition());
2131 Range r(c, ExclusiveMotion);
2132
2133 m_stickyColumn = -1;
2134
2135 for (int i = 0; i < getCount(); i++) {
2136 c = findPrevWORDStart(c.line(), c.column());
2137
2138 if (!c.isValid()) {
2139 c = KTextEditor::Cursor(0, 0);
2140 }
2141 }
2142
2143 r.endColumn = c.column();
2144 r.endLine = c.line();
2145
2146 return r;
2147}
2148
2149Range NormalViMode::motionToEndOfWord()
2150{
2151 KTextEditor::Cursor c(m_view->cursorPosition());
2152 Range r(c, InclusiveMotion);
2153
2154 m_stickyColumn = -1;
2155
2156 for (int i = 0; i < getCount(); i++) {
2157 c = findWordEnd(c.line(), c.column());
2158 }
2159
2160 if (!c.isValid()) {
2161 c = doc()->documentEnd();
2162 }
2163
2164 r.endColumn = c.column();
2165 r.endLine = c.line();
2166
2167 return r;
2168}
2169
2170Range NormalViMode::motionToEndOfWORD()
2171{
2172 KTextEditor::Cursor c(m_view->cursorPosition());
2173 Range r(c, InclusiveMotion);
2174
2175 m_stickyColumn = -1;
2176
2177 for (int i = 0; i < getCount(); i++) {
2178 c = findWORDEnd(c.line(), c.column());
2179 }
2180
2181 if (!c.isValid()) {
2182 c = doc()->documentEnd();
2183 }
2184
2185 r.endColumn = c.column();
2186 r.endLine = c.line();
2187
2188 return r;
2189}
2190
2191Range NormalViMode::motionToEndOfPrevWord()
2192{
2193 KTextEditor::Cursor c(m_view->cursorPosition());
2194 Range r(c, InclusiveMotion);
2195
2196 m_stickyColumn = -1;
2197
2198 for (int i = 0; i < getCount(); i++) {
2199 c = findPrevWordEnd(c.line(), c.column());
2200
2201 if (c.isValid()) {
2202 r.endColumn = c.column();
2203 r.endLine = c.line();
2204 } else {
2205 r.endColumn = 0;
2206 r.endLine = 0;
2207 break;
2208 }
2209 }
2210
2211 return r;
2212}
2213
2214Range NormalViMode::motionToEndOfPrevWORD()
2215{
2216 KTextEditor::Cursor c(m_view->cursorPosition());
2217 Range r(c, InclusiveMotion);
2218
2219 m_stickyColumn = -1;
2220
2221 for (int i = 0; i < getCount(); i++) {
2222 c = findPrevWORDEnd(c.line(), c.column());
2223
2224 if (c.isValid()) {
2225 r.endColumn = c.column();
2226 r.endLine = c.line();
2227 } else {
2228 r.endColumn = 0;
2229 r.endLine = 0;
2230 break;
2231 }
2232 }
2233
2234 return r;
2235}
2236
2237void NormalViMode::stickStickyColumnToEOL()
2238{
2239 if (m_keys.size() == 1) {
2240 m_stickyColumn = KateVi::EOL;
2241 }
2242}
2243
2244Range NormalViMode::motionToEOL()
2245{
2246 KTextEditor::Cursor c(m_view->cursorPosition());
2247
2248 stickStickyColumnToEOL();
2249
2250 unsigned int line = c.line() + (getCount() - 1);
2251 Range r(line, doc()->lineLength(line) - 1, InclusiveMotion);
2252
2253 return r;
2254}
2255Range NormalViMode::motionToLastNonBlank()
2256{
2257 KTextEditor::Cursor c(m_view->cursorPosition());
2258
2259 stickStickyColumnToEOL();
2260
2261 unsigned int line = c.line() + (getCount() - 1);
2262
2263 const auto text_line = doc()->plainKateTextLine(line);
2264 Range r(line, text_line.previousNonSpaceChar(text_line.length()), InclusiveMotion);
2265 return r;
2266}
2267
2268Range NormalViMode::motionToColumn0()
2269{
2270 m_stickyColumn = -1;
2271 KTextEditor::Cursor cursor(m_view->cursorPosition());
2272 Range r(cursor.line(), 0, ExclusiveMotion);
2273
2274 return r;
2275}
2276
2277Range NormalViMode::motionToFirstCharacterOfLine()
2278{
2279 m_stickyColumn = -1;
2280
2281 KTextEditor::Cursor cursor(m_view->cursorPosition());
2282 int c = getFirstNonBlank();
2283
2284 Range r(cursor.line(), c, ExclusiveMotion);
2285
2286 return r;
2287}
2288
2289Range NormalViMode::motionFindChar()
2290{
2291 m_lastTFcommand = m_keys;
2292 KTextEditor::Cursor cursor(m_view->cursorPosition());
2293 QString line = getLine();
2294
2295 m_stickyColumn = -1;
2296
2297 int matchColumn = cursor.column();
2298
2299 for (int i = 0; i < getCount(); i++) {
2300 matchColumn = line.indexOf(QStringView(m_keys).right(1), matchColumn + 1);
2301 if (matchColumn == -1) {
2302 break;
2303 }
2304 }
2305
2306 Range r;
2307
2308 if (matchColumn != -1) {
2309 r.endColumn = matchColumn;
2310 r.endLine = cursor.line();
2311 } else {
2312 return Range::invalid();
2313 }
2314
2315 return r;
2316}
2317
2318Range NormalViMode::motionFindCharBackward()
2319{
2320 m_lastTFcommand = m_keys;
2321 KTextEditor::Cursor cursor(m_view->cursorPosition());
2322 QString line = getLine();
2323
2324 m_stickyColumn = -1;
2325
2326 int matchColumn = -1;
2327
2328 int hits = 0;
2329 int i = cursor.column() - 1;
2330
2331 while (hits != getCount() && i >= 0) {
2332 if (line.at(i) == m_keys.at(m_keys.size() - 1)) {
2333 hits++;
2334 }
2335
2336 if (hits == getCount()) {
2337 matchColumn = i;
2338 }
2339
2340 i--;
2341 }
2342
2343 Range r(cursor, ExclusiveMotion);
2344
2345 if (matchColumn != -1) {
2346 r.endColumn = matchColumn;
2347 r.endLine = cursor.line();
2348 } else {
2349 return Range::invalid();
2350 }
2351
2352 return r;
2353}
2354
2355Range NormalViMode::motionToChar()
2356{
2357 m_lastTFcommand = m_keys;
2358 KTextEditor::Cursor cursor(m_view->cursorPosition());
2359 QString line = getLine();
2360
2361 m_stickyColumn = -1;
2362 Range r;
2363 r.endColumn = -1;
2364 r.endLine = -1;
2365
2366 int matchColumn = cursor.column() + (m_isRepeatedTFcommand ? 2 : 1);
2367
2368 for (int i = 0; i < getCount(); i++) {
2369 const int lastColumn = matchColumn;
2370 matchColumn = line.indexOf(m_keys.right(1), matchColumn + ((i > 0) ? 1 : 0));
2371 if (matchColumn == -1) {
2372 if (m_isRepeatedTFcommand) {
2373 matchColumn = lastColumn;
2374 } else {
2375 return Range::invalid();
2376 }
2377 break;
2378 }
2379 }
2380
2381 r.endColumn = matchColumn - 1;
2382 r.endLine = cursor.line();
2383
2384 m_isRepeatedTFcommand = false;
2385 return r;
2386}
2387
2388Range NormalViMode::motionToCharBackward()
2389{
2390 m_lastTFcommand = m_keys;
2391 KTextEditor::Cursor cursor(m_view->cursorPosition());
2392 QString line = getLine();
2393
2394 const int originalColumn = cursor.column();
2395 m_stickyColumn = -1;
2396
2397 int matchColumn = originalColumn - 1;
2398
2399 int hits = 0;
2400 int i = cursor.column() - (m_isRepeatedTFcommand ? 2 : 1);
2401
2402 Range r(cursor, ExclusiveMotion);
2403
2404 while (hits != getCount() && i >= 0) {
2405 if (line.at(i) == m_keys.at(m_keys.size() - 1)) {
2406 hits++;
2407 }
2408
2409 if (hits == getCount()) {
2410 matchColumn = i;
2411 }
2412
2413 i--;
2414 }
2415
2416 if (hits == getCount()) {
2417 r.endColumn = matchColumn + 1;
2418 r.endLine = cursor.line();
2419 } else {
2420 r.valid = false;
2421 }
2422
2423 m_isRepeatedTFcommand = false;
2424
2425 return r;
2426}
2427
2428Range NormalViMode::motionRepeatlastTF()
2429{
2430 if (!m_lastTFcommand.isEmpty()) {
2431 m_isRepeatedTFcommand = true;
2432 m_keys = m_lastTFcommand;
2433 if (m_keys.at(0) == QLatin1Char('f')) {
2434 return motionFindChar();
2435 } else if (m_keys.at(0) == QLatin1Char('F')) {
2436 return motionFindCharBackward();
2437 } else if (m_keys.at(0) == QLatin1Char('t')) {
2438 return motionToChar();
2439 } else if (m_keys.at(0) == QLatin1Char('T')) {
2440 return motionToCharBackward();
2441 }
2442 }
2443
2444 // there was no previous t/f command
2445 return Range::invalid();
2446}
2447
2448Range NormalViMode::motionRepeatlastTFBackward()
2449{
2450 if (!m_lastTFcommand.isEmpty()) {
2451 m_isRepeatedTFcommand = true;
2452 m_keys = m_lastTFcommand;
2453 if (m_keys.at(0) == QLatin1Char('f')) {
2454 return motionFindCharBackward();
2455 } else if (m_keys.at(0) == QLatin1Char('F')) {
2456 return motionFindChar();
2457 } else if (m_keys.at(0) == QLatin1Char('t')) {
2458 return motionToCharBackward();
2459 } else if (m_keys.at(0) == QLatin1Char('T')) {
2460 return motionToChar();
2461 }
2462 }
2463
2464 // there was no previous t/f command
2465 return Range::invalid();
2466}
2467
2468Range NormalViMode::motionToLineFirst()
2469{
2470 Range r(getCount() - 1, 0, InclusiveMotion);
2471 m_stickyColumn = -1;
2472
2473 if (r.endLine > doc()->lines() - 1) {
2474 r.endLine = doc()->lines() - 1;
2475 }
2476 r.jump = true;
2477
2478 return r;
2479}
2480
2481Range NormalViMode::motionToLineLast()
2482{
2483 Range r(doc()->lines() - 1, 0, InclusiveMotion);
2484 m_stickyColumn = -1;
2485
2486 // don't use getCount() here, no count and a count of 1 is different here...
2487 if (m_count != 0) {
2488 r.endLine = m_count - 1;
2489 }
2490
2491 if (r.endLine > doc()->lines() - 1) {
2492 r.endLine = doc()->lines() - 1;
2493 }
2494 r.jump = true;
2495
2496 return r;
2497}
2498
2499Range NormalViMode::motionToScreenColumn()
2500{
2501 m_stickyColumn = -1;
2502
2503 KTextEditor::Cursor c(m_view->cursorPosition());
2504
2505 int column = getCount() - 1;
2506
2507 if (doc()->lineLength(c.line()) - 1 < (int)getCount() - 1) {
2508 column = doc()->lineLength(c.line()) - 1;
2509 }
2510
2511 return Range(c.line(), column, ExclusiveMotion);
2512}
2513
2514Range NormalViMode::motionToMark()
2515{
2516 Range r;
2517
2518 m_stickyColumn = -1;
2519
2520 QChar reg = m_keys.at(m_keys.size() - 1);
2521
2522 KTextEditor::Cursor c = m_viInputModeManager->marks()->getMarkPosition(reg);
2523 if (c.isValid()) {
2524 r.endLine = c.line();
2525 r.endColumn = c.column();
2526 } else {
2527 error(i18n("Mark not set: %1", m_keys.right(1)));
2528 r.valid = false;
2529 }
2530
2531 r.jump = true;
2532
2533 return r;
2534}
2535
2536Range NormalViMode::motionToMarkLine()
2537{
2538 Range r = motionToMark();
2539 r.endColumn = getFirstNonBlank(r.endLine);
2540 r.jump = true;
2541 m_stickyColumn = -1;
2542 return r;
2543}
2544
2545Range NormalViMode::motionToMatchingItem()
2546{
2547 Range r;
2548 int lines = doc()->lines();
2549
2550 // If counted, then it's not a motion to matching item anymore,
2551 // but a motion to the N'th percentage of the document
2552 if (isCounted()) {
2553 int count = getCount();
2554 if (count > 100) {
2555 return r;
2556 }
2557 r.endLine = qRound(lines * count / 100.0) - 1;
2558 r.endColumn = 0;
2559 return r;
2560 }
2561
2562 KTextEditor::Cursor c(m_view->cursorPosition());
2563
2564 QString l = getLine();
2565 int n1 = l.indexOf(m_matchItemRegex, c.column());
2566
2567 m_stickyColumn = -1;
2568
2569 if (n1 < 0) {
2570 return Range::invalid();
2571 }
2572
2573 const auto bracketChar = l.at(n1);
2574 // use Kate's built-in matching bracket finder for brackets
2575 if (bracketChar == QLatin1Char('(') || bracketChar == QLatin1Char(')') || bracketChar == QLatin1Char('{') || bracketChar == QLatin1Char('}')
2576 || bracketChar == QLatin1Char('[') || bracketChar == QLatin1Char(']')) {
2577 // findMatchingBracket requires us to move the cursor to the
2578 // first bracket, but we don't want the cursor to really move
2579 // in case this is e.g. a yank, so restore it to its original
2580 // position afterwards.
2581 c.setColumn(n1);
2582 const KTextEditor::Cursor oldCursorPos = m_view->cursorPosition();
2583 updateCursor(c);
2584
2585 // find the matching one
2586 c = m_viewInternal->findMatchingBracket();
2587 if (c > m_view->cursorPosition()) {
2588 c.setColumn(c.column() - 1);
2589 }
2590 m_view->setCursorPosition(oldCursorPos);
2591 } else {
2592 // text item we want to find a matching item for
2593 static const QRegularExpression boundaryRegex(QStringLiteral("\\b|\\s|$"), QRegularExpression::UseUnicodePropertiesOption);
2594 const int n2 = l.indexOf(boundaryRegex, n1);
2595 QString item = l.mid(n1, n2 - n1);
2596 QString matchingItem = m_matchingItems[item];
2597
2598 int toFind = 1;
2599 int line = c.line();
2600 int column = n2 - item.length();
2601 bool reverse = false;
2602
2603 if (matchingItem.startsWith(QLatin1Char('-'))) {
2604 matchingItem.remove(0, 1); // remove the '-'
2605 reverse = true;
2606 }
2607
2608 // make sure we don't hit the text item we started the search from
2609 if (column == 0 && reverse) {
2610 column -= item.length();
2611 }
2612
2613 int itemIdx;
2614 int matchItemIdx;
2615
2616 while (toFind > 0) {
2617 if (reverse) {
2618 itemIdx = l.lastIndexOf(item, column - 1);
2619 matchItemIdx = l.lastIndexOf(matchingItem, column - 1);
2620
2621 if (itemIdx != -1 && (matchItemIdx == -1 || itemIdx > matchItemIdx)) {
2622 ++toFind;
2623 }
2624 } else {
2625 itemIdx = l.indexOf(item, column);
2626 matchItemIdx = l.indexOf(matchingItem, column);
2627
2628 if (itemIdx != -1 && (matchItemIdx == -1 || itemIdx < matchItemIdx)) {
2629 ++toFind;
2630 }
2631 }
2632
2633 if (matchItemIdx != -1 || itemIdx != -1) {
2634 if (!reverse) {
2635 column = qMin((unsigned int)itemIdx, (unsigned int)matchItemIdx);
2636 } else {
2637 column = qMax(itemIdx, matchItemIdx);
2638 }
2639 }
2640
2641 if (matchItemIdx != -1) { // match on current line
2642 if (matchItemIdx == column) {
2643 --toFind;
2644 c.setLine(line);
2645 c.setColumn(column);
2646 }
2647 } else { // no match, advance one line if possible
2648 (reverse) ? --line : ++line;
2649 column = 0;
2650
2651 if ((!reverse && line >= lines) || (reverse && line < 0)) {
2652 r.valid = false;
2653 break;
2654 } else {
2655 l = getLine(line);
2656 }
2657 }
2658 }
2659 }
2660
2661 r.endLine = c.line();
2662 r.endColumn = c.column();
2663 r.jump = true;
2664
2665 return r;
2666}
2667
2668Range NormalViMode::motionToNextBraceBlockStart()
2669{
2670 Range r;
2671
2672 m_stickyColumn = -1;
2673
2674 int line = findLineStartingWitchChar(QLatin1Char('{'), getCount());
2675
2676 if (line == -1) {
2677 return Range::invalid();
2678 }
2679
2680 r.endLine = line;
2681 r.endColumn = 0;
2682 r.jump = true;
2683
2684 if (motionWillBeUsedWithCommand()) {
2685 // Delete from cursor (inclusive) to the '{' (exclusive).
2686 // If we are on the first column, then delete the entire current line.
2687 r.motionType = ExclusiveMotion;
2688 if (m_view->cursorPosition().column() != 0) {
2689 r.endLine--;
2690 r.endColumn = doc()->lineLength(r.endLine);
2691 }
2692 }
2693
2694 return r;
2695}
2696
2697Range NormalViMode::motionToPreviousBraceBlockStart()
2698{
2699 Range r;
2700
2701 m_stickyColumn = -1;
2702
2703 int line = findLineStartingWitchChar(QLatin1Char('{'), getCount(), false);
2704
2705 if (line == -1) {
2706 return Range::invalid();
2707 }
2708
2709 r.endLine = line;
2710 r.endColumn = 0;
2711 r.jump = true;
2712
2713 if (motionWillBeUsedWithCommand()) {
2714 // With a command, do not include the { or the cursor position.
2715 r.motionType = ExclusiveMotion;
2716 }
2717
2718 return r;
2719}
2720
2721Range NormalViMode::motionToNextBraceBlockEnd()
2722{
2723 Range r;
2724
2725 m_stickyColumn = -1;
2726
2727 int line = findLineStartingWitchChar(QLatin1Char('}'), getCount());
2728
2729 if (line == -1) {
2730 return Range::invalid();
2731 }
2732
2733 r.endLine = line;
2734 r.endColumn = 0;
2735 r.jump = true;
2736
2737 if (motionWillBeUsedWithCommand()) {
2738 // Delete from cursor (inclusive) to the '}' (exclusive).
2739 // If we are on the first column, then delete the entire current line.
2740 r.motionType = ExclusiveMotion;
2741 if (m_view->cursorPosition().column() != 0) {
2742 r.endLine--;
2743 r.endColumn = doc()->lineLength(r.endLine);
2744 }
2745 }
2746
2747 return r;
2748}
2749
2750Range NormalViMode::motionToPreviousBraceBlockEnd()
2751{
2752 Range r;
2753
2754 m_stickyColumn = -1;
2755
2756 int line = findLineStartingWitchChar(QLatin1Char('}'), getCount(), false);
2757
2758 if (line == -1) {
2759 return Range::invalid();
2760 }
2761
2762 r.endLine = line;
2763 r.endColumn = 0;
2764 r.jump = true;
2765
2766 if (motionWillBeUsedWithCommand()) {
2767 r.motionType = ExclusiveMotion;
2768 }
2769
2770 return r;
2771}
2772
2773Range NormalViMode::motionToNextOccurrence()
2774{
2775 const QString word = getWordUnderCursor();
2776 Searcher *searcher = m_viInputModeManager->searcher();
2777 const Range match = searcher->findWordForMotion(word, false, getWordRangeUnderCursor().start(), getCount());
2778 if (searcher->lastSearchWrapped()) {
2779 m_view->showSearchWrappedHint(/*isReverseSearch*/ false);
2780 }
2781
2782 return Range(match.startLine, match.startColumn, ExclusiveMotion);
2783}
2784
2785Range NormalViMode::motionToPrevOccurrence()
2786{
2787 const QString word = getWordUnderCursor();
2788 Searcher *searcher = m_viInputModeManager->searcher();
2789 const Range match = searcher->findWordForMotion(word, true, getWordRangeUnderCursor().start(), getCount());
2790 if (searcher->lastSearchWrapped()) {
2791 m_view->showSearchWrappedHint(/*isReverseSearch*/ true);
2792 }
2793
2794 return Range(match.startLine, match.startColumn, ExclusiveMotion);
2795}
2796
2797Range NormalViMode::motionToFirstLineOfWindow()
2798{
2799 int lines_to_go;
2800 if (linesDisplayed() <= (unsigned int)m_viewInternal->endLine()) {
2801 lines_to_go = m_viewInternal->endLine() - linesDisplayed() - m_view->cursorPosition().line() + 1;
2802 } else {
2803 lines_to_go = -m_view->cursorPosition().line();
2804 }
2805
2806 Range r = goLineUpDown(lines_to_go);
2807 r.endColumn = getFirstNonBlank(r.endLine);
2808 return r;
2809}
2810
2811Range NormalViMode::motionToMiddleLineOfWindow()
2812{
2813 int lines_to_go;
2814 if (linesDisplayed() <= (unsigned int)m_viewInternal->endLine()) {
2815 lines_to_go = m_viewInternal->endLine() - linesDisplayed() / 2 - m_view->cursorPosition().line();
2816 } else {
2817 lines_to_go = m_viewInternal->endLine() / 2 - m_view->cursorPosition().line();
2818 }
2819
2820 Range r = goLineUpDown(lines_to_go);
2821 r.endColumn = getFirstNonBlank(r.endLine);
2822 return r;
2823}
2824
2825Range NormalViMode::motionToLastLineOfWindow()
2826{
2827 int lines_to_go;
2828 if (linesDisplayed() <= (unsigned int)m_viewInternal->endLine()) {
2829 lines_to_go = m_viewInternal->endLine() - m_view->cursorPosition().line();
2830 } else {
2831 lines_to_go = m_viewInternal->endLine() - m_view->cursorPosition().line();
2832 }
2833
2834 Range r = goLineUpDown(lines_to_go);
2835 r.endColumn = getFirstNonBlank(r.endLine);
2836 return r;
2837}
2838
2839Range NormalViMode::motionToNextVisualLine()
2840{
2841 return goVisualLineUpDown(getCount());
2842}
2843
2844Range NormalViMode::motionToPrevVisualLine()
2845{
2846 return goVisualLineUpDown(-getCount());
2847}
2848
2849Range NormalViMode::motionToPreviousSentence()
2850{
2851 KTextEditor::Cursor c = findSentenceStart();
2852 int linenum = c.line();
2853 int column = c.column();
2854 const bool skipSpaces = doc()->line(linenum).isEmpty();
2855
2856 if (skipSpaces) {
2857 linenum--;
2858 if (linenum >= 0) {
2859 column = doc()->line(linenum).size() - 1;
2860 }
2861 }
2862
2863 for (int i = linenum; i >= 0; i--) {
2864 const QString &line = doc()->line(i);
2865
2866 if (line.isEmpty() && !skipSpaces) {
2867 return Range(i, 0, InclusiveMotion);
2868 }
2869
2870 if (column < 0 && !line.isEmpty()) {
2871 column = line.size() - 1;
2872 }
2873
2874 for (int j = column; j >= 0; j--) {
2875 if (skipSpaces || QStringLiteral(".?!").indexOf(line.at(j)) != -1) {
2876 c.setLine(i);
2877 c.setColumn(j);
2878 updateCursor(c);
2879 c = findSentenceStart();
2880 return Range(c, InclusiveMotion);
2881 }
2882 }
2883 column = line.size() - 1;
2884 }
2885 return Range(0, 0, InclusiveMotion);
2886}
2887
2888Range NormalViMode::motionToNextSentence()
2889{
2890 KTextEditor::Cursor c = findSentenceEnd();
2891 int linenum = c.line();
2892 int column = c.column() + 1;
2893 const bool skipSpaces = doc()->line(linenum).isEmpty();
2894
2895 for (int i = linenum; i < doc()->lines(); i++) {
2896 const QString &line = doc()->line(i);
2897
2898 if (line.isEmpty() && !skipSpaces) {
2899 return Range(i, 0, InclusiveMotion);
2900 }
2901
2902 for (int j = column; j < line.size(); j++) {
2903 if (!line.at(j).isSpace()) {
2904 return Range(i, j, InclusiveMotion);
2905 }
2906 }
2907 column = 0;
2908 }
2909
2910 c = doc()->documentEnd();
2911 return Range(c, InclusiveMotion);
2912}
2913
2914Range NormalViMode::motionToBeforeParagraph()
2915{
2916 KTextEditor::Cursor c(m_view->cursorPosition());
2917
2918 int line = c.line();
2919
2920 m_stickyColumn = -1;
2921
2922 for (int i = 0; i < getCount(); i++) {
2923 // advance at least one line, but if there are consecutive blank lines
2924 // skip them all
2925 do {
2926 line--;
2927 } while (line >= 0 && getLine(line + 1).length() == 0);
2928 while (line > 0 && getLine(line).length() != 0) {
2929 line--;
2930 }
2931 }
2932
2933 if (line < 0) {
2934 line = 0;
2935 }
2936
2937 Range r(line, 0, InclusiveMotion);
2938
2939 return r;
2940}
2941
2942Range NormalViMode::motionToAfterParagraph()
2943{
2944 KTextEditor::Cursor c(m_view->cursorPosition());
2945
2946 int line = c.line();
2947
2948 m_stickyColumn = -1;
2949
2950 for (int i = 0; i < getCount(); i++) {
2951 // advance at least one line, but if there are consecutive blank lines
2952 // skip them all
2953 do {
2954 line++;
2955 } while (line <= doc()->lines() - 1 && getLine(line - 1).length() == 0);
2956 while (line < doc()->lines() - 1 && getLine(line).length() != 0) {
2957 line++;
2958 }
2959 }
2960
2961 if (line >= doc()->lines()) {
2962 line = doc()->lines() - 1;
2963 }
2964
2965 // if we ended up on the last line, the cursor should be placed on the last column
2966 int column = (line == doc()->lines() - 1) ? qMax(getLine(line).length() - 1, 0) : 0;
2967
2968 return Range(line, column, InclusiveMotion);
2969}
2970
2971Range NormalViMode::motionToIncrementalSearchMatch()
2972{
2973 return Range(m_positionWhenIncrementalSearchBegan.line(),
2974 m_positionWhenIncrementalSearchBegan.column(),
2975 m_view->cursorPosition().line(),
2976 m_view->cursorPosition().column(),
2977 ExclusiveMotion);
2978}
2979
2980////////////////////////////////////////////////////////////////////////////////
2981// TEXT OBJECTS
2982////////////////////////////////////////////////////////////////////////////////
2983
2984Range NormalViMode::textObjectAWord()
2985{
2986 KTextEditor::Cursor c(m_view->cursorPosition());
2987
2988 KTextEditor::Cursor c1 = c;
2989
2990 bool startedOnSpace = false;
2991 if (doc()->characterAt(c).isSpace()) {
2992 startedOnSpace = true;
2993 } else {
2994 c1 = findPrevWordStart(c.line(), c.column() + 1, true);
2995 if (!c1.isValid()) {
2996 c1 = KTextEditor::Cursor(0, 0);
2997 }
2998 }
3000 for (int i = 1; i <= getCount(); i++) {
3001 c2 = findWordEnd(c2.line(), c2.column());
3002 }
3003 if (!c1.isValid() || !c2.isValid()) {
3004 return Range::invalid();
3005 }
3006 // Adhere to some of Vim's bizarre rules of whether to swallow ensuing spaces or not.
3007 // Don't ask ;)
3008 const KTextEditor::Cursor nextWordStart = findNextWordStart(c2.line(), c2.column());
3009 if (nextWordStart.isValid() && nextWordStart.line() == c2.line()) {
3010 if (!startedOnSpace) {
3011 c2 = KTextEditor::Cursor(nextWordStart.line(), nextWordStart.column() - 1);
3012 }
3013 } else {
3014 c2 = KTextEditor::Cursor(c2.line(), doc()->lineLength(c2.line()) - 1);
3015 }
3016 bool swallowCarriageReturnAtEndOfLine = false;
3017 if (c2.line() != c.line() && c2.column() == doc()->lineLength(c2.line()) - 1) {
3018 // Greedily descend to the next line, so as to swallow the carriage return on this line.
3019 c2 = KTextEditor::Cursor(c2.line() + 1, 0);
3020 swallowCarriageReturnAtEndOfLine = true;
3021 }
3022 const bool swallowPrecedingSpaces =
3023 (c2.column() == doc()->lineLength(c2.line()) - 1 && !doc()->characterAt(c2).isSpace()) || startedOnSpace || swallowCarriageReturnAtEndOfLine;
3024 if (swallowPrecedingSpaces) {
3025 if (c1.column() != 0) {
3026 const KTextEditor::Cursor previousNonSpace = findPrevWordEnd(c.line(), c.column());
3027 if (previousNonSpace.isValid() && previousNonSpace.line() == c1.line()) {
3028 c1 = KTextEditor::Cursor(previousNonSpace.line(), previousNonSpace.column() + 1);
3029 } else if (startedOnSpace || swallowCarriageReturnAtEndOfLine) {
3030 c1 = KTextEditor::Cursor(c1.line(), 0);
3031 }
3032 }
3033 }
3034
3035 return Range(c1, c2, !swallowCarriageReturnAtEndOfLine ? InclusiveMotion : ExclusiveMotion);
3036}
3037
3038Range NormalViMode::textObjectInnerWord()
3039{
3040 KTextEditor::Cursor c(m_view->cursorPosition());
3041
3042 KTextEditor::Cursor c1 = findPrevWordStart(c.line(), c.column() + 1, true);
3043 if (!c1.isValid()) {
3044 c1 = KTextEditor::Cursor(0, 0);
3045 }
3046 // need to start search in column-1 because it might be a one-character word
3047 KTextEditor::Cursor c2(c.line(), c.column() - 1);
3048
3049 for (int i = 0; i < getCount(); i++) {
3050 c2 = findWordEnd(c2.line(), c2.column(), true);
3051 }
3052
3053 if (!c2.isValid()) {
3054 c2 = doc()->documentEnd();
3055 }
3056
3057 // sanity check
3058 if (c1.line() != c2.line() || c1.column() > c2.column()) {
3059 return Range::invalid();
3060 }
3061 return Range(c1, c2, InclusiveMotion);
3062}
3063
3064Range NormalViMode::textObjectAWORD()
3065{
3066 KTextEditor::Cursor c(m_view->cursorPosition());
3067
3068 KTextEditor::Cursor c1 = c;
3069
3070 bool startedOnSpace = false;
3071 if (doc()->characterAt(c).isSpace()) {
3072 startedOnSpace = true;
3073 } else {
3074 c1 = findPrevWORDStart(c.line(), c.column() + 1, true);
3075 if (!c1.isValid()) {
3076 c1 = KTextEditor::Cursor(0, 0);
3077 }
3078 }
3080 for (int i = 1; i <= getCount(); i++) {
3081 c2 = findWORDEnd(c2.line(), c2.column());
3082 }
3083 if (!c1.isValid() || !c2.isValid()) {
3084 return Range::invalid();
3085 }
3086 // Adhere to some of Vim's bizarre rules of whether to swallow ensuing spaces or not.
3087 // Don't ask ;)
3088 const KTextEditor::Cursor nextWordStart = findNextWordStart(c2.line(), c2.column());
3089 if (nextWordStart.isValid() && nextWordStart.line() == c2.line()) {
3090 if (!startedOnSpace) {
3091 c2 = KTextEditor::Cursor(nextWordStart.line(), nextWordStart.column() - 1);
3092 }
3093 } else {
3094 c2 = KTextEditor::Cursor(c2.line(), doc()->lineLength(c2.line()) - 1);
3095 }
3096 bool swallowCarriageReturnAtEndOfLine = false;
3097 if (c2.line() != c.line() && c2.column() == doc()->lineLength(c2.line()) - 1) {
3098 // Greedily descend to the next line, so as to swallow the carriage return on this line.
3099 c2 = KTextEditor::Cursor(c2.line() + 1, 0);
3100 swallowCarriageReturnAtEndOfLine = true;
3101 }
3102 const bool swallowPrecedingSpaces =
3103 (c2.column() == doc()->lineLength(c2.line()) - 1 && !doc()->characterAt(c2).isSpace()) || startedOnSpace || swallowCarriageReturnAtEndOfLine;
3104 if (swallowPrecedingSpaces) {
3105 if (c1.column() != 0) {
3106 const KTextEditor::Cursor previousNonSpace = findPrevWORDEnd(c.line(), c.column());
3107 if (previousNonSpace.isValid() && previousNonSpace.line() == c1.line()) {
3108 c1 = KTextEditor::Cursor(previousNonSpace.line(), previousNonSpace.column() + 1);
3109 } else if (startedOnSpace || swallowCarriageReturnAtEndOfLine) {
3110 c1 = KTextEditor::Cursor(c1.line(), 0);
3111 }
3112 }
3113 }
3114
3115 return Range(c1, c2, !swallowCarriageReturnAtEndOfLine ? InclusiveMotion : ExclusiveMotion);
3116}
3117
3118Range NormalViMode::textObjectInnerWORD()
3119{
3120 KTextEditor::Cursor c(m_view->cursorPosition());
3121
3122 KTextEditor::Cursor c1 = findPrevWORDStart(c.line(), c.column() + 1, true);
3123 if (!c1.isValid()) {
3124 c1 = KTextEditor::Cursor(0, 0);
3125 }
3126 KTextEditor::Cursor c2(c);
3127
3128 for (int i = 0; i < getCount(); i++) {
3129 c2 = findWORDEnd(c2.line(), c2.column(), true);
3130 }
3131
3132 if (!c2.isValid()) {
3133 c2 = doc()->documentEnd();
3134 }
3135
3136 // sanity check
3137 if (c1.line() != c2.line() || c1.column() > c2.column()) {
3138 return Range::invalid();
3139 }
3140 return Range(c1, c2, InclusiveMotion);
3141}
3142
3143KTextEditor::Cursor NormalViMode::findSentenceStart()
3144{
3145 KTextEditor::Cursor c(m_view->cursorPosition());
3146 int linenum = c.line();
3147 int column = c.column();
3148 int prev = column;
3149
3150 for (int i = linenum; i >= 0; i--) {
3151 const QString &line = doc()->line(i);
3152 const int lineLength = line.size();
3153 if (i != linenum) {
3154 column = lineLength;
3155 }
3156
3157 // An empty line is the end of a paragraph.
3158 if (line.isEmpty()) {
3159 return KTextEditor::Cursor((i != linenum) ? i + 1 : i, prev);
3160 }
3161
3162 prev = column;
3163 for (int j = column; j >= 0; j--) {
3164 if (j == lineLength || line.at(j).isSpace()) {
3165 int lastSpace = j--;
3166 for (; j >= 0 && QStringLiteral("\"')]").indexOf(line.at(j)) != -1; j--) {
3167 ;
3168 }
3169
3170 if (j >= 0 && QStringLiteral(".!?").indexOf(line.at(j)) != -1) {
3171 if (lastSpace == lineLength) {
3172 // If the line ends with one of .!?, then the sentence starts from the next line.
3173 return KTextEditor::Cursor(i + 1, 0);
3174 }
3175
3176 return KTextEditor::Cursor(i, prev);
3177 }
3178 j = lastSpace;
3179 } else {
3180 prev = j;
3181 }
3182 }
3183 }
3184
3185 return KTextEditor::Cursor(0, 0);
3186}
3187
3188KTextEditor::Cursor NormalViMode::findSentenceEnd()
3189{
3190 KTextEditor::Cursor c(m_view->cursorPosition());
3191 int linenum = c.line();
3192 int column = c.column();
3193 int j = 0;
3194 int prev = 0;
3195
3196 for (int i = linenum; i < doc()->lines(); i++) {
3197 const QString &line = doc()->line(i);
3198
3199 // An empty line is the end of a paragraph.
3200 if (line.isEmpty()) {
3201 return KTextEditor::Cursor(linenum, j);
3202 }
3203
3204 // Iterating over the line to reach any '.', '!', '?'
3205 for (j = column; j < line.size(); j++) {
3206 if (QStringLiteral(".!?").indexOf(line.at(j)) != -1) {
3207 prev = j++;
3208 // Skip possible closing characters.
3209 for (; j < line.size() && QStringLiteral("\"')]").indexOf(line.at(j)) != -1; j++) {
3210 ;
3211 }
3212
3213 if (j >= line.size()) {
3214 return KTextEditor::Cursor(i, j - 1);
3215 }
3216
3217 // And hopefully we're done...
3218 if (line.at(j).isSpace()) {
3219 return KTextEditor::Cursor(i, j - 1);
3220 }
3221 j = prev;
3222 }
3223 }
3224 linenum = i;
3225 prev = column;
3226 column = 0;
3227 }
3228
3229 return KTextEditor::Cursor(linenum, j - 1);
3230}
3231
3232KTextEditor::Cursor NormalViMode::findParagraphStart()
3233{
3234 KTextEditor::Cursor c(m_view->cursorPosition());
3235 const bool firstBlank = doc()->line(c.line()).isEmpty();
3236 int prev = c.line();
3237
3238 for (int i = prev; i >= 0; i--) {
3239 if (doc()->line(i).isEmpty()) {
3240 if (i != prev) {
3241 prev = i + 1;
3242 }
3243
3244 /* Skip consecutive empty lines. */
3245 if (firstBlank) {
3246 i--;
3247 for (; i >= 0 && doc()->line(i).isEmpty(); i--, prev--) {
3248 ;
3249 }
3250 }
3251 return KTextEditor::Cursor(prev, 0);
3252 }
3253 }
3254 return KTextEditor::Cursor(0, 0);
3255}
3256
3257KTextEditor::Cursor NormalViMode::findParagraphEnd()
3258{
3259 KTextEditor::Cursor c(m_view->cursorPosition());
3260 int prev = c.line();
3261 int lines = doc()->lines();
3262 const bool firstBlank = doc()->line(prev).isEmpty();
3263
3264 for (int i = prev; i < lines; i++) {
3265 if (doc()->line(i).isEmpty()) {
3266 if (i != prev) {
3267 prev = i - 1;
3268 }
3269
3270 /* Skip consecutive empty lines. */
3271 if (firstBlank) {
3272 i++;
3273 for (; i < lines && doc()->line(i).isEmpty(); i++, prev++) {
3274 ;
3275 }
3276 }
3277 int length = doc()->lineLength(prev);
3278 return KTextEditor::Cursor(prev, (length <= 0) ? 0 : length - 1);
3279 }
3280 }
3281 return doc()->documentEnd();
3282}
3283
3284Range NormalViMode::textObjectInnerSentence()
3285{
3286 Range r;
3287 KTextEditor::Cursor c1 = findSentenceStart();
3288 KTextEditor::Cursor c2 = findSentenceEnd();
3289 updateCursor(c1);
3290
3291 r.startLine = c1.line();
3292 r.startColumn = c1.column();
3293 r.endLine = c2.line();
3294 r.endColumn = c2.column();
3295 return r;
3296}
3297
3298Range NormalViMode::textObjectASentence()
3299{
3300 int i;
3301 Range r = textObjectInnerSentence();
3302 const QString &line = doc()->line(r.endLine);
3303
3304 // Skip whitespaces and tabs.
3305 for (i = r.endColumn + 1; i < line.size(); i++) {
3306 if (!line.at(i).isSpace()) {
3307 break;
3308 }
3309 }
3310 r.endColumn = i - 1;
3311
3312 // Remove preceding spaces.
3313 if (r.startColumn != 0) {
3314 if (r.endColumn == line.size() - 1 && !line.at(r.endColumn).isSpace()) {
3315 const QString &line = doc()->line(r.startLine);
3316 for (i = r.startColumn - 1; i >= 0; i--) {
3317 if (!line.at(i).isSpace()) {
3318 break;
3319 }
3320 }
3321 r.startColumn = i + 1;
3322 }
3323 }
3324 return r;
3325}
3326
3327Range NormalViMode::textObjectInnerParagraph()
3328{
3329 Range r;
3330 KTextEditor::Cursor c1 = findParagraphStart();
3331 KTextEditor::Cursor c2 = findParagraphEnd();
3332 updateCursor(c1);
3333
3334 r.startLine = c1.line();
3335 r.startColumn = c1.column();
3336 r.endLine = c2.line();
3337 r.endColumn = c2.column();
3338 return r;
3339}
3340
3341Range NormalViMode::textObjectAParagraph()
3342{
3343 Range r = textObjectInnerParagraph();
3344 int lines = doc()->lines();
3345
3346 if (r.endLine + 1 < lines) {
3347 // If the next line is empty, remove all subsequent empty lines.
3348 // Otherwise we'll grab the next paragraph.
3349 if (doc()->line(r.endLine + 1).isEmpty()) {
3350 for (int i = r.endLine + 1; i < lines && doc()->line(i).isEmpty(); i++) {
3351 r.endLine++;
3352 }
3353 r.endColumn = 0;
3354 } else {
3355 KTextEditor::Cursor prev = m_view->cursorPosition();
3356 KTextEditor::Cursor c(r.endLine + 1, 0);
3357 updateCursor(c);
3358 c = findParagraphEnd();
3359 updateCursor(prev);
3360 r.endLine = c.line();
3361 r.endColumn = c.column();
3362 }
3363 } else if (doc()->lineLength(r.startLine) > 0) {
3364 // We went too far, but maybe we can grab previous empty lines.
3365 for (int i = r.startLine - 1; i >= 0 && doc()->line(i).isEmpty(); i--) {
3366 r.startLine--;
3367 }
3368 r.startColumn = 0;
3369 updateCursor(KTextEditor::Cursor(r.startLine, r.startColumn));
3370 } else {
3371 // We went too far and we're on empty lines, do nothing.
3372 return Range::invalid();
3373 }
3374 return r;
3375}
3376
3377Range NormalViMode::textObjectAQuoteDouble()
3378{
3379 return findSurroundingQuotes(QLatin1Char('"'), false);
3380}
3381
3382Range NormalViMode::textObjectInnerQuoteDouble()
3383{
3384 return findSurroundingQuotes(QLatin1Char('"'), true);
3385}
3386
3387Range NormalViMode::textObjectAQuoteSingle()
3388{
3389 return findSurroundingQuotes(QLatin1Char('\''), false);
3390}
3391
3392Range NormalViMode::textObjectInnerQuoteSingle()
3393{
3394 return findSurroundingQuotes(QLatin1Char('\''), true);
3395}
3396
3397Range NormalViMode::textObjectABackQuote()
3398{
3399 return findSurroundingQuotes(QLatin1Char('`'), false);
3400}
3401
3402Range NormalViMode::textObjectInnerBackQuote()
3403{
3404 return findSurroundingQuotes(QLatin1Char('`'), true);
3405}
3406
3407Range NormalViMode::textObjectAParen()
3408{
3409 return findSurroundingBrackets(QLatin1Char('('), QLatin1Char(')'), false, QLatin1Char('('), QLatin1Char(')'));
3410}
3411
3412Range NormalViMode::textObjectInnerParen()
3413{
3414 return findSurroundingBrackets(QLatin1Char('('), QLatin1Char(')'), true, QLatin1Char('('), QLatin1Char(')'));
3415}
3416
3417Range NormalViMode::textObjectABracket()
3418{
3419 return findSurroundingBrackets(QLatin1Char('['), QLatin1Char(']'), false, QLatin1Char('['), QLatin1Char(']'));
3420}
3421
3422Range NormalViMode::textObjectInnerBracket()
3423{
3424 return findSurroundingBrackets(QLatin1Char('['), QLatin1Char(']'), true, QLatin1Char('['), QLatin1Char(']'));
3425}
3426
3427Range NormalViMode::textObjectACurlyBracket()
3428{
3429 return findSurroundingBrackets(QLatin1Char('{'), QLatin1Char('}'), false, QLatin1Char('{'), QLatin1Char('}'));
3430}
3431
3432Range NormalViMode::textObjectInnerCurlyBracket()
3433{
3434 const Range allBetweenCurlyBrackets = findSurroundingBrackets(QLatin1Char('{'), QLatin1Char('}'), true, QLatin1Char('{'), QLatin1Char('}'));
3435 // Emulate the behaviour of vim, which tries to leave the closing bracket on its own line
3436 // if it was originally on a line different to that of the opening bracket.
3437 Range innerCurlyBracket(allBetweenCurlyBrackets);
3438
3439 if (innerCurlyBracket.startLine != innerCurlyBracket.endLine) {
3440 const bool openingBraceIsLastCharOnLine = innerCurlyBracket.startColumn == doc()->line(innerCurlyBracket.startLine).length();
3441 const bool stuffToDeleteIsAllOnEndLine = openingBraceIsLastCharOnLine && innerCurlyBracket.endLine == innerCurlyBracket.startLine + 1;
3442 const QString textLeadingClosingBracket = doc()->line(innerCurlyBracket.endLine).mid(0, innerCurlyBracket.endColumn + 1);
3443 const bool closingBracketHasLeadingNonWhitespace = !textLeadingClosingBracket.trimmed().isEmpty();
3444 if (stuffToDeleteIsAllOnEndLine) {
3445 if (!closingBracketHasLeadingNonWhitespace) {
3446 // Nothing there to select - abort.
3447 return Range::invalid();
3448 } else {
3449 // Shift the beginning of the range to the start of the line containing the closing bracket.
3450 innerCurlyBracket.startLine++;
3451 innerCurlyBracket.startColumn = 0;
3452 }
3453 } else {
3454 if (openingBraceIsLastCharOnLine && !closingBracketHasLeadingNonWhitespace) {
3455 innerCurlyBracket.startLine++;
3456 innerCurlyBracket.startColumn = 0;
3457 m_lastMotionWasLinewiseInnerBlock = true;
3458 }
3459 {
3460 // The line containing the end bracket is left alone if the end bracket is preceded by just whitespace,
3461 // else we need to delete everything (i.e. end up with "{}")
3462 if (!closingBracketHasLeadingNonWhitespace) {
3463 // Shrink the endpoint of the range so that it ends at the end of the line above,
3464 // leaving the closing bracket on its own line.
3465 innerCurlyBracket.endLine--;
3466 innerCurlyBracket.endColumn = doc()->line(innerCurlyBracket.endLine).length();
3467 }
3468 }
3469 }
3470 }
3471 return innerCurlyBracket;
3472}
3473
3474Range NormalViMode::textObjectAInequalitySign()
3475{
3476 return findSurroundingBrackets(QLatin1Char('<'), QLatin1Char('>'), false, QLatin1Char('<'), QLatin1Char('>'));
3477}
3478
3479Range NormalViMode::textObjectInnerInequalitySign()
3480{
3481 return findSurroundingBrackets(QLatin1Char('<'), QLatin1Char('>'), true, QLatin1Char('<'), QLatin1Char('>'));
3482}
3483
3484Range NormalViMode::textObjectAComma()
3485{
3486 return textObjectComma(false);
3487}
3488
3489Range NormalViMode::textObjectInnerComma()
3490{
3491 return textObjectComma(true);
3492}
3493
3494QRegularExpression NormalViMode::generateMatchingItemRegex() const
3495{
3496 QString pattern(QStringLiteral("\\[|\\]|\\{|\\}|\\(|\\)|"));
3497
3498 for (QString s : std::as_const(m_matchingItems)) {
3499 if (s.startsWith(QLatin1Char('-'))) {
3500 s.remove(0, 1);
3501 }
3502 s.replace(QLatin1Char('*'), QStringLiteral("\\*"));
3503 s.replace(QLatin1Char('+'), QStringLiteral("\\+"));
3504 s.replace(QLatin1Char('['), QStringLiteral("\\["));
3505 s.replace(QLatin1Char(']'), QStringLiteral("\\]"));
3506 s.replace(QLatin1Char('('), QStringLiteral("\\("));
3507 s.replace(QLatin1Char(')'), QStringLiteral("\\)"));
3508 s.replace(QLatin1Char('{'), QStringLiteral("\\{"));
3509 s.replace(QLatin1Char('}'), QStringLiteral("\\}"));
3510
3511 s.append(QLatin1Char('|'));
3512 pattern.append(s);
3513 }
3514 // remove extra "|" at the end
3515 pattern.chop(1);
3516
3518}
3519
3520// returns the operation mode that should be used. this is decided by using the following heuristic:
3521// 1. if we're in visual block mode, it should be Block
3522// 2. if we're in visual line mode OR the range spans several lines, it should be LineWise
3523// 3. if neither of these is true, CharWise is returned
3524// 4. there are some motion that makes all operator charwise, if we have one of them mode will be CharWise
3525OperationMode NormalViMode::getOperationMode() const
3526{
3527 OperationMode m = CharWise;
3528
3529 if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualBlockMode) {
3530 m = Block;
3531 } else if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualLineMode
3532 || (m_commandRange.startLine != m_commandRange.endLine && m_viInputModeManager->getCurrentViMode() != ViMode::VisualMode)) {
3533 m = LineWise;
3534 }
3535
3536 if (m_commandWithMotion && !m_linewiseCommand) {
3537 m = CharWise;
3538 }
3539
3540 if (m_lastMotionWasLinewiseInnerBlock) {
3541 m = LineWise;
3542 }
3543
3544 return m;
3545}
3546
3547bool NormalViMode::paste(PasteLocation pasteLocation, bool isgPaste, bool isIndentedPaste)
3548{
3549 KTextEditor::Cursor pasteAt(m_view->cursorPosition());
3550 KTextEditor::Cursor cursorAfterPaste = pasteAt;
3551 QChar reg = getChosenRegister(UnnamedRegister);
3552
3553 OperationMode m = getRegisterFlag(reg);
3554 QString textToInsert = getRegisterContent(reg);
3555 const bool isTextMultiLine = textToInsert.count(QLatin1Char('\n')) > 0;
3556
3557 // In temporary normal mode, p/P act as gp/gP.
3558 isgPaste |= m_viInputModeManager->getTemporaryNormalMode();
3559
3560 if (textToInsert.isEmpty()) {
3561 error(i18n("Nothing in register %1", reg.toLower()));
3562 return false;
3563 }
3564
3565 if (getCount() > 1) {
3566 textToInsert = textToInsert.repeated(getCount()); // FIXME: does this make sense for blocks?
3567 }
3568
3569 if (m == LineWise) {
3570 pasteAt.setColumn(0);
3571 if (isIndentedPaste) {
3572 // Note that this does indeed work if there is no non-whitespace on the current line or if
3573 // the line is empty!
3574 static const QRegularExpression nonWhitespaceRegex(QStringLiteral("[^\\s]"));
3575 const QString pasteLineString = doc()->line(pasteAt.line());
3576 const QString leadingWhiteSpaceOnCurrentLine = pasteLineString.mid(0, pasteLineString.indexOf(nonWhitespaceRegex));
3577 const QString leadingWhiteSpaceOnFirstPastedLine = textToInsert.mid(0, textToInsert.indexOf(nonWhitespaceRegex));
3578 // QString has no "left trim" method, bizarrely.
3579 while (textToInsert[0].isSpace()) {
3580 textToInsert = textToInsert.mid(1);
3581 }
3582 textToInsert.prepend(leadingWhiteSpaceOnCurrentLine);
3583 // Remove the last \n, temporarily: we're going to alter the indentation of each pasted line
3584 // by doing a search and replace on '\n's, but don't want to alter this one.
3585 textToInsert.chop(1);
3586 textToInsert.replace(QLatin1Char('\n') + leadingWhiteSpaceOnFirstPastedLine, QLatin1Char('\n') + leadingWhiteSpaceOnCurrentLine);
3587 textToInsert.append(QLatin1Char('\n')); // Re-add the temporarily removed last '\n'.
3588 }
3589 if (pasteLocation == AfterCurrentPosition) {
3590 textToInsert.chop(1); // remove the last \n
3591 pasteAt.setColumn(doc()->lineLength(pasteAt.line())); // paste after the current line and ...
3592 textToInsert.prepend(QLatin1Char('\n')); // ... prepend a \n, so the text starts on a new line
3593
3594 cursorAfterPaste.setLine(cursorAfterPaste.line() + 1);
3595 }
3596 if (isgPaste) {
3597 cursorAfterPaste.setLine(cursorAfterPaste.line() + textToInsert.count(QLatin1Char('\n')));
3598 }
3599 } else {
3600 if (pasteLocation == AfterCurrentPosition) {
3601 // Move cursor forward one before we paste. The position after the paste must also
3602 // be updated accordingly.
3603 if (getLine(pasteAt.line()).length() > 0) {
3604 pasteAt.setColumn(pasteAt.column() + 1);
3605 }
3606 cursorAfterPaste = pasteAt;
3607 }
3608 const bool leaveCursorAtStartOfPaste = isTextMultiLine && !isgPaste;
3609 if (!leaveCursorAtStartOfPaste) {
3610 cursorAfterPaste = cursorPosAtEndOfPaste(pasteAt, textToInsert);
3611 if (!isgPaste) {
3612 cursorAfterPaste.setColumn(cursorAfterPaste.column() - 1);
3613 }
3614 }
3615 }
3616
3617 doc()->editStart();
3618 if (m_view->selection()) {
3619 pasteAt = m_view->selectionRange().start();
3620 doc()->removeText(m_view->selectionRange());
3621 }
3622 doc()->insertText(pasteAt, textToInsert, m == Block);
3623 doc()->editEnd();
3624
3625 if (cursorAfterPaste.line() >= doc()->lines()) {
3626 cursorAfterPaste.setLine(doc()->lines() - 1);
3627 }
3628 updateCursor(cursorAfterPaste);
3629
3630 return true;
3631}
3632
3633KTextEditor::Cursor NormalViMode::cursorPosAtEndOfPaste(const KTextEditor::Cursor pasteLocation, const QString &pastedText)
3634{
3635 KTextEditor::Cursor cAfter = pasteLocation;
3636 const int lineCount = pastedText.count(QLatin1Char('\n')) + 1;
3637 if (lineCount == 1) {
3638 cAfter.setColumn(cAfter.column() + pastedText.length());
3639 } else {
3640 const int lastLineLength = pastedText.size() - (pastedText.lastIndexOf(QLatin1Char('\n')) + 1);
3641 cAfter.setColumn(lastLineLength);
3642 cAfter.setLine(cAfter.line() + lineCount - 1);
3643 }
3644 return cAfter;
3645}
3646
3647void NormalViMode::joinLines(unsigned int from, unsigned int to) const
3648{
3649 // make sure we don't try to join lines past the document end
3650 if (to >= (unsigned int)(doc()->lines())) {
3651 to = doc()->lines() - 1;
3652 }
3653
3654 // joining one line is a no-op
3655 if (from == to) {
3656 return;
3657 }
3658
3659 doc()->joinLines(from, to);
3660}
3661
3662void NormalViMode::reformatLines(unsigned int from, unsigned int to) const
3663{
3664 // BUG #340550: Do not remove empty lines when reformatting
3665 KTextEditor::DocumentPrivate *document = doc();
3666 auto isNonEmptyLine = [](QStringView text) {
3667 for (int i = 0; i < text.length(); ++i) {
3668 if (!text.at(i).isSpace()) {
3669 return true;
3670 }
3671 }
3672
3673 return false;
3674 };
3675 for (; from < to; ++from) {
3676 if (isNonEmptyLine(document->line(from))) {
3677 break;
3678 }
3679 }
3680 for (; to > from; --to) {
3681 if (isNonEmptyLine(document->line(to))) {
3682 break;
3683 }
3684 }
3685
3686 document->editStart();
3687 joinLines(from, to);
3688 document->wrapText(from, to);
3689 document->editEnd();
3690}
3691
3693{
3694 if (line < 0) {
3695 line = m_view->cursorPosition().line();
3696 }
3697
3698 // doc()->plainKateTextLine returns NULL if the line is out of bounds.
3699 Kate::TextLine l = doc()->plainKateTextLine(line);
3700 int c = l.firstChar();
3701 return (c < 0) ? 0 : c;
3702}
3703
3704// Tries to shrinks toShrink so that it fits tightly around rangeToShrinkTo.
3705void NormalViMode::shrinkRangeAroundCursor(Range &toShrink, const Range &rangeToShrinkTo) const
3706{
3707 if (!toShrink.valid || !rangeToShrinkTo.valid) {
3708 return;
3709 }
3710 KTextEditor::Cursor cursorPos = m_view->cursorPosition();
3711 if (rangeToShrinkTo.startLine >= cursorPos.line()) {
3712 if (rangeToShrinkTo.startLine > cursorPos.line()) {
3713 // Does not surround cursor; aborting.
3714 return;
3715 }
3716 Q_ASSERT(rangeToShrinkTo.startLine == cursorPos.line());
3717 if (rangeToShrinkTo.startColumn > cursorPos.column()) {
3718 // Does not surround cursor; aborting.
3719 return;
3720 }
3721 }
3722 if (rangeToShrinkTo.endLine <= cursorPos.line()) {
3723 if (rangeToShrinkTo.endLine < cursorPos.line()) {
3724 // Does not surround cursor; aborting.
3725 return;
3726 }
3727 Q_ASSERT(rangeToShrinkTo.endLine == cursorPos.line());
3728 if (rangeToShrinkTo.endColumn < cursorPos.column()) {
3729 // Does not surround cursor; aborting.
3730 return;
3731 }
3732 }
3733
3734 if (toShrink.startLine <= rangeToShrinkTo.startLine) {
3735 if (toShrink.startLine < rangeToShrinkTo.startLine) {
3736 toShrink.startLine = rangeToShrinkTo.startLine;
3737 toShrink.startColumn = rangeToShrinkTo.startColumn;
3738 }
3739 Q_ASSERT(toShrink.startLine == rangeToShrinkTo.startLine);
3740 if (toShrink.startColumn < rangeToShrinkTo.startColumn) {
3741 toShrink.startColumn = rangeToShrinkTo.startColumn;
3742 }
3743 }
3744 if (toShrink.endLine >= rangeToShrinkTo.endLine) {
3745 if (toShrink.endLine > rangeToShrinkTo.endLine) {
3746 toShrink.endLine = rangeToShrinkTo.endLine;
3747 toShrink.endColumn = rangeToShrinkTo.endColumn;
3748 }
3749 Q_ASSERT(toShrink.endLine == rangeToShrinkTo.endLine);
3750 if (toShrink.endColumn > rangeToShrinkTo.endColumn) {
3751 toShrink.endColumn = rangeToShrinkTo.endColumn;
3752 }
3753 }
3754}
3755
3756Range NormalViMode::textObjectComma(bool inner) const
3757{
3758 // Basic algorithm: look left and right of the cursor for all combinations
3759 // of enclosing commas and the various types of brackets, and pick the pair
3760 // closest to the cursor that surrounds the cursor.
3761 Range r(0, 0, m_view->doc()->lines(), m_view->doc()->line(m_view->doc()->lastLine()).length(), InclusiveMotion);
3762
3763 shrinkRangeAroundCursor(r, findSurroundingQuotes(QLatin1Char(','), inner));
3764 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('('), QLatin1Char(')'), inner, QLatin1Char('('), QLatin1Char(')')));
3765 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('{'), QLatin1Char('}'), inner, QLatin1Char('{'), QLatin1Char('}')));
3766 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char(','), QLatin1Char(')'), inner, QLatin1Char('('), QLatin1Char(')')));
3767 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char(','), QLatin1Char(']'), inner, QLatin1Char('['), QLatin1Char(']')));
3768 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char(','), QLatin1Char('}'), inner, QLatin1Char('{'), QLatin1Char('}')));
3769 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('('), QLatin1Char(','), inner, QLatin1Char('('), QLatin1Char(')')));
3770 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('['), QLatin1Char(','), inner, QLatin1Char('['), QLatin1Char(']')));
3771 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('{'), QLatin1Char(','), inner, QLatin1Char('{'), QLatin1Char('}')));
3772 return r;
3773}
3774
3775void NormalViMode::updateYankHighlightAttrib()
3776{
3777 if (!m_highlightYankAttribute) {
3778 m_highlightYankAttribute = new KTextEditor::Attribute;
3779 }
3780 const QColor &yankedColor = m_view->rendererConfig()->savedLineColor();
3781 m_highlightYankAttribute->setBackground(yankedColor);
3783 mouseInAttribute->setFontBold(true);
3784 m_highlightYankAttribute->setDynamicAttribute(KTextEditor::Attribute::ActivateMouseIn, mouseInAttribute);
3785 m_highlightYankAttribute->dynamicAttribute(KTextEditor::Attribute::ActivateMouseIn)->setBackground(yankedColor);
3786}
3787
3788void NormalViMode::highlightYank(const Range &range, const OperationMode mode)
3789{
3790 clearYankHighlight();
3791
3792 // current MovingRange doesn't support block mode selection so split the
3793 // block range into per-line ranges
3794 if (mode == Block) {
3795 for (int i = range.startLine; i <= range.endLine; i++) {
3796 addHighlightYank(KTextEditor::Range(i, range.startColumn, i, range.endColumn));
3797 }
3798 } else {
3799 addHighlightYank(KTextEditor::Range(range.startLine, range.startColumn, range.endLine, range.endColumn));
3800 }
3801}
3802
3803void NormalViMode::addHighlightYank(KTextEditor::Range yankRange)
3804{
3805 KTextEditor::MovingRange *highlightedYank = m_view->doc()->newMovingRange(yankRange, Kate::TextRange::DoNotExpand);
3806 highlightedYank->setView(m_view); // show only in this view
3807 highlightedYank->setAttributeOnlyForViews(true);
3808 // use z depth defined in moving ranges interface
3809 highlightedYank->setZDepth(-10000.0);
3810 highlightedYank->setAttribute(m_highlightYankAttribute);
3811
3812 highlightedYankForDocument().insert(highlightedYank);
3813}
3814
3815void NormalViMode::clearYankHighlight()
3816{
3817 QSet<KTextEditor::MovingRange *> &pHighlightedYanks = highlightedYankForDocument();
3818 qDeleteAll(pHighlightedYanks);
3819 pHighlightedYanks.clear();
3820}
3821
3822QSet<KTextEditor::MovingRange *> &NormalViMode::highlightedYankForDocument()
3823{
3824 // Work around the fact that both Normal and Visual mode will have their own m_highlightedYank -
3825 // make Normal's the canonical one.
3826 return m_viInputModeManager->getViNormalMode()->m_highlightedYanks;
3827}
3828
3829bool NormalViMode::waitingForRegisterOrCharToSearch()
3830{
3831 // r, q, @ are never preceded by operators. There will always be a keys size of 1 for them.
3832 // f, t, F, T can be preceded by a delete/replace/yank/indent operator. size = 2 in that case.
3833 // f, t, F, T can be preceded by 'g' case/formatting operators. size = 3 in that case.
3834 const int keysSize = m_keys.size();
3835 if (keysSize < 1) {
3836 // Just being defensive there.
3837 return false;
3838 }
3839 if (keysSize > 1) {
3840 // Multi-letter operation.
3841 QChar cPrefix = m_keys[0];
3842 if (keysSize == 2) {
3843 // delete/replace/yank/indent operator?
3844 if (cPrefix != QLatin1Char('c') && cPrefix != QLatin1Char('d') && cPrefix != QLatin1Char('y') && cPrefix != QLatin1Char('=')
3845 && cPrefix != QLatin1Char('>') && cPrefix != QLatin1Char('<')) {
3846 return false;
3847 }
3848 } else if (keysSize == 3) {
3849 // We need to look deeper. Is it a g motion?
3850 QChar cNextfix = m_keys[1];
3851 if (cPrefix != QLatin1Char('g')
3852 || (cNextfix != QLatin1Char('U') && cNextfix != QLatin1Char('u') && cNextfix != QLatin1Char('~') && cNextfix != QLatin1Char('q')
3853 && cNextfix != QLatin1Char('w') && cNextfix != QLatin1Char('@'))) {
3854 return false;
3855 }
3856 } else {
3857 return false;
3858 }
3859 }
3860
3861 QChar ch = m_keys[keysSize - 1];
3862 return (ch == QLatin1Char('f') || ch == QLatin1Char('t') || ch == QLatin1Char('F')
3863 || ch == QLatin1Char('T')
3864 // c/d prefix unapplicable for the following cases.
3865 || (keysSize == 1 && (ch == QLatin1Char('r') || ch == QLatin1Char('q') || ch == QLatin1Char('@'))));
3866}
3867
3868void NormalViMode::textInserted(KTextEditor::Document *document, KTextEditor::Range range)
3869{
3870 if (m_viInputModeManager->view()->viewInputMode() != KTextEditor::View::ViInputMode) {
3871 return;
3872 }
3873
3874 Q_UNUSED(document)
3875 const bool isInsertReplaceMode =
3876 (m_viInputModeManager->getCurrentViMode() == ViMode::InsertMode || m_viInputModeManager->getCurrentViMode() == ViMode::ReplaceMode);
3877 const bool continuesInsertion = range.start().line() == m_currentChangeEndMarker.line() && range.start().column() == m_currentChangeEndMarker.column();
3878 const bool beginsWithNewline = doc()->text(range).at(0) == QLatin1Char('\n');
3879 if (!continuesInsertion) {
3880 KTextEditor::Cursor newBeginMarkerPos = range.start();
3881 if (beginsWithNewline && !isInsertReplaceMode) {
3882 // Presumably a linewise paste, in which case we ignore the leading '\n'
3883 newBeginMarkerPos = KTextEditor::Cursor(newBeginMarkerPos.line() + 1, 0);
3884 }
3885 m_viInputModeManager->marks()->setStartEditYanked(newBeginMarkerPos);
3886 }
3887 m_viInputModeManager->marks()->setLastChange(range.start());
3888 KTextEditor::Cursor editEndMarker = range.end();
3889 if (!isInsertReplaceMode) {
3890 editEndMarker.setColumn(editEndMarker.column() - 1);
3891 }
3892 m_viInputModeManager->marks()->setFinishEditYanked(editEndMarker);
3893 m_currentChangeEndMarker = range.end();
3894 if (m_isUndo) {
3895 const bool addsMultipleLines = range.start().line() != range.end().line();
3896 m_viInputModeManager->marks()->setStartEditYanked(KTextEditor::Cursor(m_viInputModeManager->marks()->getStartEditYanked().line(), 0));
3897 if (addsMultipleLines) {
3898 m_viInputModeManager->marks()->setFinishEditYanked(KTextEditor::Cursor(m_viInputModeManager->marks()->getFinishEditYanked().line() + 1, 0));
3899 m_viInputModeManager->marks()->setLastChange(KTextEditor::Cursor(m_viInputModeManager->marks()->getLastChange().line() + 1, 0));
3900 } else {
3901 m_viInputModeManager->marks()->setFinishEditYanked(KTextEditor::Cursor(m_viInputModeManager->marks()->getFinishEditYanked().line(), 0));
3902 m_viInputModeManager->marks()->setLastChange(KTextEditor::Cursor(m_viInputModeManager->marks()->getLastChange().line(), 0));
3903 }
3904 }
3905}
3906
3907void NormalViMode::textRemoved(KTextEditor::Document *document, KTextEditor::Range range)
3908{
3909 if (m_viInputModeManager->view()->viewInputMode() != KTextEditor::View::ViInputMode) {
3910 return;
3911 }
3912
3913 Q_UNUSED(document);
3914 const bool isInsertReplaceMode =
3915 (m_viInputModeManager->getCurrentViMode() == ViMode::InsertMode || m_viInputModeManager->getCurrentViMode() == ViMode::ReplaceMode);
3916 m_viInputModeManager->marks()->setLastChange(range.start());
3917 if (!isInsertReplaceMode) {
3918 // Don't go resetting [ just because we did a Ctrl-h!
3919 m_viInputModeManager->marks()->setStartEditYanked(range.start());
3920 } else {
3921 // Don't go disrupting our continued insertion just because we did a Ctrl-h!
3922 m_currentChangeEndMarker = range.start();
3923 }
3924 m_viInputModeManager->marks()->setFinishEditYanked(range.start());
3925 if (m_isUndo) {
3926 // Slavishly follow Vim's weird rules: if an undo removes several lines, then all markers should
3927 // be at the beginning of the line after the last line removed, else they should at the beginning
3928 // of the line above that.
3929 const int markerLineAdjustment = (range.start().line() != range.end().line()) ? 1 : 0;
3930 m_viInputModeManager->marks()->setStartEditYanked(
3931 KTextEditor::Cursor(m_viInputModeManager->marks()->getStartEditYanked().line() + markerLineAdjustment, 0));
3932 m_viInputModeManager->marks()->setFinishEditYanked(
3933 KTextEditor::Cursor(m_viInputModeManager->marks()->getFinishEditYanked().line() + markerLineAdjustment, 0));
3934 m_viInputModeManager->marks()->setLastChange(KTextEditor::Cursor(m_viInputModeManager->marks()->getLastChange().line() + markerLineAdjustment, 0));
3935 }
3936}
3937
3938void NormalViMode::undoBeginning()
3939{
3940 m_isUndo = true;
3941}
3942
3943void NormalViMode::undoEnded()
3944{
3945 m_isUndo = false;
3946}
3947
3948bool NormalViMode::executeKateCommand(const QString &command)
3949{
3950 KTextEditor::Command *p = KateCmd::self()->queryCommand(command);
3951
3952 if (!p) {
3953 return false;
3954 }
3955
3956 QString msg;
3957 return p->exec(m_view, command, msg);
3958}
3959
3960#define ADDCMD(STR, FUNC, FLGS) Command(QStringLiteral(STR), &NormalViMode::FUNC, FLGS)
3961
3962#define ADDMOTION(STR, FUNC, FLGS) Motion(QStringLiteral(STR), &NormalViMode::FUNC, FLGS)
3963
3964const std::vector<Command> &NormalViMode::commands()
3965{
3966 // init once, is expensive
3967 static const std::vector<Command> global{
3968 ADDCMD("a", commandEnterInsertModeAppend, IS_CHANGE),
3969 ADDCMD("A", commandEnterInsertModeAppendEOL, IS_CHANGE),
3970 ADDCMD("i", commandEnterInsertMode, IS_CHANGE),
3971 ADDCMD("<insert>", commandEnterInsertMode, IS_CHANGE),
3972 ADDCMD("I", commandEnterInsertModeBeforeFirstNonBlankInLine, IS_CHANGE),
3973 ADDCMD("gi", commandEnterInsertModeLast, IS_CHANGE),
3974 ADDCMD("v", commandEnterVisualMode, 0),
3975 ADDCMD("V", commandEnterVisualLineMode, 0),
3976 ADDCMD("<c-v>", commandEnterVisualBlockMode, 0),
3977 ADDCMD("gv", commandReselectVisual, SHOULD_NOT_RESET),
3978 ADDCMD("o", commandOpenNewLineUnder, IS_CHANGE),
3979 ADDCMD("O", commandOpenNewLineOver, IS_CHANGE),
3980 ADDCMD("J", commandJoinLines, IS_CHANGE),
3981 ADDCMD("c", commandChange, IS_CHANGE | NEEDS_MOTION),
3982 ADDCMD("C", commandChangeToEOL, IS_CHANGE),
3983 ADDCMD("cc", commandChangeLine, IS_CHANGE),
3984 ADDCMD("s", commandSubstituteChar, IS_CHANGE),
3985 ADDCMD("S", commandSubstituteLine, IS_CHANGE),
3986 ADDCMD("dd", commandDeleteLine, IS_CHANGE),
3987 ADDCMD("d", commandDelete, IS_CHANGE | NEEDS_MOTION),
3988 ADDCMD("D", commandDeleteToEOL, IS_CHANGE),
3989 ADDCMD("x", commandDeleteChar, IS_CHANGE),
3990 ADDCMD("<delete>", commandDeleteChar, IS_CHANGE),
3991 ADDCMD("X", commandDeleteCharBackward, IS_CHANGE),
3992 ADDCMD("gu", commandMakeLowercase, IS_CHANGE | NEEDS_MOTION),
3993 ADDCMD("guu", commandMakeLowercaseLine, IS_CHANGE),
3994 ADDCMD("gU", commandMakeUppercase, IS_CHANGE | NEEDS_MOTION),
3995 ADDCMD("gUU", commandMakeUppercaseLine, IS_CHANGE),
3996 ADDCMD("y", commandYank, NEEDS_MOTION),
3997 ADDCMD("yy", commandYankLine, 0),
3998 ADDCMD("Y", commandYankToEOL, 0),
3999 ADDCMD("p", commandPaste, IS_CHANGE),
4000 ADDCMD("P", commandPasteBefore, IS_CHANGE),
4001 ADDCMD("gp", commandgPaste, IS_CHANGE),
4002 ADDCMD("gP", commandgPasteBefore, IS_CHANGE),
4003 ADDCMD("]p", commandIndentedPaste, IS_CHANGE),
4004 ADDCMD("[p", commandIndentedPasteBefore, IS_CHANGE),
4005 ADDCMD("r.", commandReplaceCharacter, IS_CHANGE | REGEX_PATTERN),
4006 ADDCMD("R", commandEnterReplaceMode, IS_CHANGE),
4007 ADDCMD(":", commandSwitchToCmdLine, 0),
4008 ADDCMD("u", commandUndo, 0),
4009 ADDCMD("<c-r>", commandRedo, 0),
4010 ADDCMD("U", commandRedo, 0),
4011 ADDCMD("m.", commandSetMark, REGEX_PATTERN),
4012 ADDCMD(">>", commandIndentLine, IS_CHANGE),
4013 ADDCMD("<<", commandUnindentLine, IS_CHANGE),
4014 ADDCMD(">", commandIndentLines, IS_CHANGE | NEEDS_MOTION),
4015 ADDCMD("<", commandUnindentLines, IS_CHANGE | NEEDS_MOTION),
4016 ADDCMD("<c-f>", commandScrollPageDown, 0),
4017 ADDCMD("<pagedown>", commandScrollPageDown, 0),
4018 ADDCMD("<c-b>", commandScrollPageUp, 0),
4019 ADDCMD("<pageup>", commandScrollPageUp, 0),
4020 ADDCMD("<c-u>", commandScrollHalfPageUp, 0),
4021 ADDCMD("<c-d>", commandScrollHalfPageDown, 0),
4022 ADDCMD("z.", commandCenterViewOnNonBlank, 0),
4023 ADDCMD("zz", commandCenterViewOnCursor, 0),
4024 ADDCMD("z<return>", commandTopViewOnNonBlank, 0),
4025 ADDCMD("zt", commandTopViewOnCursor, 0),
4026 ADDCMD("z-", commandBottomViewOnNonBlank, 0),
4027 ADDCMD("zb", commandBottomViewOnCursor, 0),
4028 ADDCMD("ga", commandPrintCharacterCode, SHOULD_NOT_RESET),
4029 ADDCMD(".", commandRepeatLastChange, 0),
4030 ADDCMD("==", commandAlignLine, IS_CHANGE),
4031 ADDCMD("=", commandAlignLines, IS_CHANGE | NEEDS_MOTION),
4032 ADDCMD("~", commandChangeCase, IS_CHANGE),
4033 ADDCMD("g~", commandChangeCaseRange, IS_CHANGE | NEEDS_MOTION),
4034 ADDCMD("g~~", commandChangeCaseLine, IS_CHANGE),
4035 ADDCMD("<c-a>", commandAddToNumber, IS_CHANGE),
4036 ADDCMD("<c-x>", commandSubtractFromNumber, IS_CHANGE),
4037 ADDCMD("<c-o>", commandGoToPrevJump, CAN_LAND_INSIDE_FOLDING_RANGE),
4038 ADDCMD("<c-i>", commandGoToNextJump, CAN_LAND_INSIDE_FOLDING_RANGE),
4039
4040 ADDCMD("<c-w>h", commandSwitchToLeftView, 0),
4041 ADDCMD("<c-w><c-h>", commandSwitchToLeftView, 0),
4042 ADDCMD("<c-w><left>", commandSwitchToLeftView, 0),
4043 ADDCMD("<c-w>j", commandSwitchToDownView, 0),
4044 ADDCMD("<c-w><c-j>", commandSwitchToDownView, 0),
4045 ADDCMD("<c-w><down>", commandSwitchToDownView, 0),
4046 ADDCMD("<c-w>k", commandSwitchToUpView, 0),
4047 ADDCMD("<c-w><c-k>", commandSwitchToUpView, 0),
4048 ADDCMD("<c-w><up>", commandSwitchToUpView, 0),
4049 ADDCMD("<c-w>l", commandSwitchToRightView, 0),
4050 ADDCMD("<c-w><c-l>", commandSwitchToRightView, 0),
4051 ADDCMD("<c-w><right>", commandSwitchToRightView, 0),
4052 ADDCMD("<c-w>w", commandSwitchToNextView, 0),
4053 ADDCMD("<c-w><c-w>", commandSwitchToNextView, 0),
4054
4055 ADDCMD("<c-w>s", commandSplitHoriz, 0),
4056 ADDCMD("<c-w>S", commandSplitHoriz, 0),
4057 ADDCMD("<c-w><c-s>", commandSplitHoriz, 0),
4058 ADDCMD("<c-w>v", commandSplitVert, 0),
4059 ADDCMD("<c-w><c-v>", commandSplitVert, 0),
4060 ADDCMD("<c-w>c", commandCloseView, 0),
4061
4062 ADDCMD("gt", commandSwitchToNextTab, 0),
4063 ADDCMD("gT", commandSwitchToPrevTab, 0),
4064
4065 ADDCMD("gqq", commandFormatLine, IS_CHANGE),
4066 ADDCMD("gq", commandFormatLines, IS_CHANGE | NEEDS_MOTION),
4067
4068 ADDCMD("zo", commandExpandLocal, 0),
4069 ADDCMD("zc", commandCollapseLocal, 0),
4070 ADDCMD("za", commandToggleRegionVisibility, 0),
4071 ADDCMD("zr", commandExpandAll, 0),
4072 ADDCMD("zm", commandCollapseToplevelNodes, 0),
4073
4074 ADDCMD("q.", commandStartRecordingMacro, REGEX_PATTERN),
4075 ADDCMD("@.", commandReplayMacro, REGEX_PATTERN),
4076
4077 ADDCMD("ZZ", commandCloseWrite, 0),
4078 ADDCMD("ZQ", commandCloseNocheck, 0),
4079 };
4080 return global;
4081}
4082
4083const std::vector<Motion> &NormalViMode::motions()
4084{
4085 // init once, is expensive
4086 static const std::vector<Motion> global{
4087 // regular motions
4088 ADDMOTION("h", motionLeft, 0),
4089 ADDMOTION("<left>", motionLeft, 0),
4090 ADDMOTION("<backspace>", motionLeft, 0),
4091 ADDMOTION("j", motionDown, 0),
4092 ADDMOTION("<down>", motionDown, 0),
4093 ADDMOTION("<enter>", motionDownToFirstNonBlank, 0),
4094 ADDMOTION("<return>", motionDownToFirstNonBlank, 0),
4095 ADDMOTION("k", motionUp, 0),
4096 ADDMOTION("<up>", motionUp, 0),
4097 ADDMOTION("-", motionUpToFirstNonBlank, 0),
4098 ADDMOTION("+", motionDownToFirstNonBlank, 0),
4099 ADDMOTION("l", motionRight, 0),
4100 ADDMOTION("<right>", motionRight, 0),
4101 ADDMOTION(" ", motionRight, 0),
4102 ADDMOTION("$", motionToEOL, 0),
4103 ADDMOTION("g_", motionToLastNonBlank, 0),
4104 ADDMOTION("<end>", motionToEOL, 0),
4105 ADDMOTION("0", motionToColumn0, 0),
4106 ADDMOTION("<home>", motionToColumn0, 0),
4107 ADDMOTION("^", motionToFirstCharacterOfLine, 0),
4108 ADDMOTION("f.", motionFindChar, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
4109 ADDMOTION("F.", motionFindCharBackward, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
4110 ADDMOTION("t.", motionToChar, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
4111 ADDMOTION("T.", motionToCharBackward, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
4112 ADDMOTION(";", motionRepeatlastTF, CAN_LAND_INSIDE_FOLDING_RANGE),
4113 ADDMOTION(",", motionRepeatlastTFBackward, CAN_LAND_INSIDE_FOLDING_RANGE),
4114 ADDMOTION("n", motionFindNext, CAN_LAND_INSIDE_FOLDING_RANGE),
4115 ADDMOTION("N", motionFindPrev, CAN_LAND_INSIDE_FOLDING_RANGE),
4116 ADDMOTION("gg", motionToLineFirst, 0),
4117 ADDMOTION("G", motionToLineLast, 0),
4118 ADDMOTION("w", motionWordForward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4119 ADDMOTION("W", motionWORDForward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4120 ADDMOTION("<c-right>", motionWordForward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4121 ADDMOTION("<c-left>", motionWordBackward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4122 ADDMOTION("b", motionWordBackward, CAN_LAND_INSIDE_FOLDING_RANGE),
4123 ADDMOTION("B", motionWORDBackward, CAN_LAND_INSIDE_FOLDING_RANGE),
4124 ADDMOTION("e", motionToEndOfWord, CAN_LAND_INSIDE_FOLDING_RANGE),
4125 ADDMOTION("E", motionToEndOfWORD, CAN_LAND_INSIDE_FOLDING_RANGE),
4126 ADDMOTION("ge", motionToEndOfPrevWord, CAN_LAND_INSIDE_FOLDING_RANGE),
4127 ADDMOTION("gE", motionToEndOfPrevWORD, CAN_LAND_INSIDE_FOLDING_RANGE),
4128 ADDMOTION("|", motionToScreenColumn, 0),
4129 ADDMOTION("%", motionToMatchingItem, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4130 ADDMOTION("`[a-zA-Z^><\\.\\[\\]]", motionToMark, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
4131 ADDMOTION("'[a-zA-Z^><]", motionToMarkLine, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
4132 ADDMOTION("[[", motionToPreviousBraceBlockStart, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4133 ADDMOTION("]]", motionToNextBraceBlockStart, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4134 ADDMOTION("[]", motionToPreviousBraceBlockEnd, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4135 ADDMOTION("][", motionToNextBraceBlockEnd, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4136 ADDMOTION("*", motionToNextOccurrence, CAN_LAND_INSIDE_FOLDING_RANGE),
4137 ADDMOTION("#", motionToPrevOccurrence, CAN_LAND_INSIDE_FOLDING_RANGE),
4138 ADDMOTION("H", motionToFirstLineOfWindow, 0),
4139 ADDMOTION("M", motionToMiddleLineOfWindow, 0),
4140 ADDMOTION("L", motionToLastLineOfWindow, 0),
4141 ADDMOTION("gj", motionToNextVisualLine, 0),
4142 ADDMOTION("g<down>", motionToNextVisualLine, 0),
4143 ADDMOTION("gk", motionToPrevVisualLine, 0),
4144 ADDMOTION("g<up>", motionToPrevVisualLine, 0),
4145 ADDMOTION("(", motionToPreviousSentence, CAN_LAND_INSIDE_FOLDING_RANGE),
4146 ADDMOTION(")", motionToNextSentence, CAN_LAND_INSIDE_FOLDING_RANGE),
4147 ADDMOTION("{", motionToBeforeParagraph, CAN_LAND_INSIDE_FOLDING_RANGE),
4148 ADDMOTION("}", motionToAfterParagraph, CAN_LAND_INSIDE_FOLDING_RANGE),
4149
4150 // text objects
4151 ADDMOTION("iw", textObjectInnerWord, 0),
4152 ADDMOTION("aw", textObjectAWord, IS_NOT_LINEWISE),
4153 ADDMOTION("iW", textObjectInnerWORD, 0),
4154 ADDMOTION("aW", textObjectAWORD, IS_NOT_LINEWISE),
4155 ADDMOTION("is", textObjectInnerSentence, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4156 ADDMOTION("as", textObjectASentence, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4157 ADDMOTION("ip", textObjectInnerParagraph, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4158 ADDMOTION("ap", textObjectAParagraph, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4159 ADDMOTION("i\"", textObjectInnerQuoteDouble, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4160 ADDMOTION("a\"", textObjectAQuoteDouble, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4161 ADDMOTION("i'", textObjectInnerQuoteSingle, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4162 ADDMOTION("a'", textObjectAQuoteSingle, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4163 ADDMOTION("i`", textObjectInnerBackQuote, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4164 ADDMOTION("a`", textObjectABackQuote, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4165 ADDMOTION("i[()b]", textObjectInnerParen, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4166 ADDMOTION("a[()b]", textObjectAParen, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4167 ADDMOTION("i[{}B]", textObjectInnerCurlyBracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4168 ADDMOTION("a[{}B]", textObjectACurlyBracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4169 ADDMOTION("i[><]", textObjectInnerInequalitySign, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4170 ADDMOTION("a[><]", textObjectAInequalitySign, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4171 ADDMOTION("i[\\[\\]]", textObjectInnerBracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4172 ADDMOTION("a[\\[\\]]", textObjectABracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4173 ADDMOTION("i,", textObjectInnerComma, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4174 ADDMOTION("a,", textObjectAComma, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4175
4176 ADDMOTION("/<enter>", motionToIncrementalSearchMatch, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4177 ADDMOTION("?<enter>", motionToIncrementalSearchMatch, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4178 };
4179 return global;
4180}
A class which provides customized text decorations.
Definition attribute.h:51
@ ActivateMouseIn
Activate attribute on mouse in.
Definition attribute.h:246
An Editor command line command.
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
Backend of KTextEditor::Document related public KTextEditor interfaces.
KTextEditor::Cursor documentEnd() const override
End position of the document.
void joinLines(uint first, uint last)
Unwrap a range of lines.
KTextEditor::MovingRange * newMovingRange(KTextEditor::Range range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors=KTextEditor::MovingRange::DoNotExpand, KTextEditor::MovingRange::EmptyBehavior emptyBehavior=KTextEditor::MovingRange::AllowEmpty) override
Create a new moving range for this document.
QString text(KTextEditor::Range range, bool blockwise=false) const override
Get the document content within the given range.
QString line(int line) const override
Get a single text line.
int lastLine() const
gets the last line number (lines() - 1)
int lines() const override
Get the count of lines of the document.
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.
Kate::TextLine plainKateTextLine(int i)
Return line lineno.
void textInsertedRange(KTextEditor::Document *document, KTextEditor::Range range)
The document emits this signal whenever text was inserted.
Kate::TextLine kateTextLine(int i)
Same as plainKateTextLine(), except that it is made sure the line is highlighted.
bool editEnd()
End a editor operation.
int lineLength(int line) const override
Get the length of a given line in characters.
A KParts derived class representing a text document.
Definition document.h:284
void aboutToInvalidateMovingInterfaceContent(KTextEditor::Document *document)
This signal is emitted before the ranges of a document are invalidated and the revisions are deleted ...
A range that is bound to a specific Document, and maintains its position.
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.
An object representing a section of text, from one Cursor to another.
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.
constexpr bool isValid() const noexcept
Validity check.
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
int lineToVisibleLine(int line) const
Convert a text buffer line to a visible line number.
int visibleLineToLine(int visibleLine) const
Convert a visible line number to a line number in the text buffer.
Class representing a single text line.
int firstChar() const
Returns the position of the first non-whitespace character.
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18n(const char *text, const TYPE &arg...)
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
const QList< QKeySequence > & end()
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
const_reference at(qsizetype i) const const
void clear()
bool isEmpty() const const
void push_back(parameter_type value)
void remove(qsizetype i, qsizetype n)
qsizetype size() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void clear()
iterator insert(const T &value)
void push(const T &t)
T & top()
qsizetype count() const const
QString & append(QChar ch)
const QChar at(qsizetype position) const const
void chop(qsizetype n)
void clear()
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)
QString right(qsizetype n) const const
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)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void repaint()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:01:56 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.