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

KDE's Doxygen guidelines are available online.