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 m_view->slotFoldToplevelNodes();
1881 return true;
1882}
1883
1884bool NormalViMode::commandStartRecordingMacro()
1885{
1886 const QChar reg = m_keys[m_keys.size() - 1];
1887 m_viInputModeManager->macroRecorder()->start(reg);
1888 return true;
1889}
1890
1891bool NormalViMode::commandReplayMacro()
1892{
1893 // "@<registername>" will have been added to the log; it needs to be cleared
1894 // *before* we replay the macro keypresses, else it can cause an infinite loop
1895 // if the macro contains a "."
1896 m_viInputModeManager->clearCurrentChangeLog();
1897 const QChar reg = m_keys[m_keys.size() - 1];
1898 const unsigned int count = getCount();
1899 resetParser();
1900 doc()->editStart();
1901 for (unsigned int i = 0; i < count; i++) {
1902 m_viInputModeManager->macroRecorder()->replay(reg);
1903 }
1904 doc()->editEnd();
1905 return true;
1906}
1907
1908bool NormalViMode::commandCloseNocheck()
1909{
1910 return executeKateCommand(QStringLiteral("q!"));
1911}
1912
1913bool NormalViMode::commandCloseWrite()
1914{
1915 return executeKateCommand(QStringLiteral("wq"));
1916}
1917
1918bool NormalViMode::commandCollapseLocal()
1919{
1920 int line = m_view->cursorPosition().line();
1921 bool actionDone = false;
1922 while (!actionDone && line > -1) {
1923 actionDone = m_view->foldLine(line--).isValid();
1924 }
1925 return true;
1926}
1927
1928bool NormalViMode::commandExpandAll()
1929{
1930 // FIXME: is toplevel same as all?
1931 m_view->slotExpandToplevelNodes();
1932 return true;
1933}
1934
1935bool NormalViMode::commandExpandLocal()
1936{
1937 int line = m_view->cursorPosition().line();
1938 return m_view->unfoldLine(line);
1939}
1940
1941bool NormalViMode::commandToggleRegionVisibility()
1942{
1943 // FIXME: is this equivalent to Vim (or a desired change)?
1944 m_view->slotToggleFolding();
1945 return true;
1946}
1947
1948////////////////////////////////////////////////////////////////////////////////
1949// MOTIONS
1950////////////////////////////////////////////////////////////////////////////////
1951
1952Range NormalViMode::motionDown()
1953{
1954 return goLineDown();
1955}
1956
1957Range NormalViMode::motionUp()
1958{
1959 return goLineUp();
1960}
1961
1962Range NormalViMode::motionLeft()
1963{
1964 KTextEditor::Cursor cursor(m_view->cursorPosition());
1965 m_stickyColumn = -1;
1966 Range r(cursor, ExclusiveMotion);
1967 r.endColumn -= getCount();
1968
1969 if (r.endColumn < 0) {
1970 r.endColumn = 0;
1971 }
1972
1973 return r;
1974}
1975
1976Range NormalViMode::motionRight()
1977{
1978 KTextEditor::Cursor cursor(m_view->cursorPosition());
1979 m_stickyColumn = -1;
1980 Range r(cursor, ExclusiveMotion);
1981 r.endColumn += getCount();
1982
1983 // make sure end position isn't > line length
1984 if (r.endColumn > doc()->lineLength(r.endLine)) {
1985 r.endColumn = doc()->lineLength(r.endLine);
1986 }
1987
1988 return r;
1989}
1990
1991Range NormalViMode::motionPageDown()
1992{
1993 KTextEditor::Cursor c(m_view->cursorPosition());
1994 Range r(c, InclusiveMotion);
1995 r.endLine += linesDisplayed();
1996
1997 if (r.endLine >= doc()->lines()) {
1998 r.endLine = doc()->lines() - 1;
1999 }
2000 return r;
2001}
2002
2003Range NormalViMode::motionPageUp()
2004{
2005 KTextEditor::Cursor c(m_view->cursorPosition());
2006 Range r(c, InclusiveMotion);
2007 r.endLine -= linesDisplayed();
2008
2009 if (r.endLine < 0) {
2010 r.endLine = 0;
2011 }
2012 return r;
2013}
2014
2015Range NormalViMode::motionHalfPageDown()
2016{
2017 if (commandScrollHalfPageDown()) {
2018 KTextEditor::Cursor c = m_view->cursorPosition();
2019 m_commandRange.endLine = c.line();
2020 m_commandRange.endColumn = c.column();
2021 return m_commandRange;
2022 }
2023 return Range::invalid();
2024}
2025
2026Range NormalViMode::motionHalfPageUp()
2027{
2028 if (commandScrollHalfPageUp()) {
2029 KTextEditor::Cursor c = m_view->cursorPosition();
2030 m_commandRange.endLine = c.line();
2031 m_commandRange.endColumn = c.column();
2032 return m_commandRange;
2033 }
2034 return Range::invalid();
2035}
2036
2037Range NormalViMode::motionDownToFirstNonBlank()
2038{
2039 Range r = goLineDown();
2040 r.endColumn = getFirstNonBlank(r.endLine);
2041 return r;
2042}
2043
2044Range NormalViMode::motionUpToFirstNonBlank()
2045{
2046 Range r = goLineUp();
2047 r.endColumn = getFirstNonBlank(r.endLine);
2048 return r;
2049}
2050
2051Range NormalViMode::motionWordForward()
2052{
2053 KTextEditor::Cursor c(m_view->cursorPosition());
2054 Range r(c, ExclusiveMotion);
2055
2056 m_stickyColumn = -1;
2057
2058 // Special case: If we're already on the very last character in the document, the motion should be
2059 // inclusive so the last character gets included
2060 if (c.line() == doc()->lines() - 1 && c.column() == doc()->lineLength(c.line()) - 1) {
2061 r.motionType = InclusiveMotion;
2062 } else {
2063 for (int i = 0; i < getCount(); i++) {
2064 c = findNextWordStart(c.line(), c.column());
2065
2066 // stop when at the last char in the document
2067 if (!c.isValid()) {
2068 c = doc()->documentEnd();
2069 // if we still haven't "used up the count", make the motion inclusive, so that the last char
2070 // is included
2071 if (i < getCount()) {
2072 r.motionType = InclusiveMotion;
2073 }
2074 break;
2075 }
2076 }
2077 }
2078
2079 r.endColumn = c.column();
2080 r.endLine = c.line();
2081
2082 return r;
2083}
2084
2085Range NormalViMode::motionWordBackward()
2086{
2087 KTextEditor::Cursor c(m_view->cursorPosition());
2088 Range r(c, ExclusiveMotion);
2089
2090 m_stickyColumn = -1;
2091
2092 for (int i = 0; i < getCount(); i++) {
2093 c = findPrevWordStart(c.line(), c.column());
2094
2095 if (!c.isValid()) {
2096 c = KTextEditor::Cursor(0, 0);
2097 break;
2098 }
2099 }
2100
2101 r.endColumn = c.column();
2102 r.endLine = c.line();
2103
2104 return r;
2105}
2106
2107Range NormalViMode::motionWORDForward()
2108{
2109 KTextEditor::Cursor c(m_view->cursorPosition());
2110 Range r(c, ExclusiveMotion);
2111
2112 m_stickyColumn = -1;
2113
2114 for (int i = 0; i < getCount(); i++) {
2115 c = findNextWORDStart(c.line(), c.column());
2116
2117 // stop when at the last char in the document
2118 if (c.line() == doc()->lines() - 1 && c.column() == doc()->lineLength(c.line()) - 1) {
2119 break;
2120 }
2121 }
2122
2123 r.endColumn = c.column();
2124 r.endLine = c.line();
2125
2126 return r;
2127}
2128
2129Range NormalViMode::motionWORDBackward()
2130{
2131 KTextEditor::Cursor c(m_view->cursorPosition());
2132 Range r(c, ExclusiveMotion);
2133
2134 m_stickyColumn = -1;
2135
2136 for (int i = 0; i < getCount(); i++) {
2137 c = findPrevWORDStart(c.line(), c.column());
2138
2139 if (!c.isValid()) {
2140 c = KTextEditor::Cursor(0, 0);
2141 }
2142 }
2143
2144 r.endColumn = c.column();
2145 r.endLine = c.line();
2146
2147 return r;
2148}
2149
2150Range NormalViMode::motionToEndOfWord()
2151{
2152 KTextEditor::Cursor c(m_view->cursorPosition());
2153 Range r(c, InclusiveMotion);
2154
2155 m_stickyColumn = -1;
2156
2157 for (int i = 0; i < getCount(); i++) {
2158 c = findWordEnd(c.line(), c.column());
2159 }
2160
2161 if (!c.isValid()) {
2162 c = doc()->documentEnd();
2163 }
2164
2165 r.endColumn = c.column();
2166 r.endLine = c.line();
2167
2168 return r;
2169}
2170
2171Range NormalViMode::motionToEndOfWORD()
2172{
2173 KTextEditor::Cursor c(m_view->cursorPosition());
2174 Range r(c, InclusiveMotion);
2175
2176 m_stickyColumn = -1;
2177
2178 for (int i = 0; i < getCount(); i++) {
2179 c = findWORDEnd(c.line(), c.column());
2180 }
2181
2182 if (!c.isValid()) {
2183 c = doc()->documentEnd();
2184 }
2185
2186 r.endColumn = c.column();
2187 r.endLine = c.line();
2188
2189 return r;
2190}
2191
2192Range NormalViMode::motionToEndOfPrevWord()
2193{
2194 KTextEditor::Cursor c(m_view->cursorPosition());
2195 Range r(c, InclusiveMotion);
2196
2197 m_stickyColumn = -1;
2198
2199 for (int i = 0; i < getCount(); i++) {
2200 c = findPrevWordEnd(c.line(), c.column());
2201
2202 if (c.isValid()) {
2203 r.endColumn = c.column();
2204 r.endLine = c.line();
2205 } else {
2206 r.endColumn = 0;
2207 r.endLine = 0;
2208 break;
2209 }
2210 }
2211
2212 return r;
2213}
2214
2215Range NormalViMode::motionToEndOfPrevWORD()
2216{
2217 KTextEditor::Cursor c(m_view->cursorPosition());
2218 Range r(c, InclusiveMotion);
2219
2220 m_stickyColumn = -1;
2221
2222 for (int i = 0; i < getCount(); i++) {
2223 c = findPrevWORDEnd(c.line(), c.column());
2224
2225 if (c.isValid()) {
2226 r.endColumn = c.column();
2227 r.endLine = c.line();
2228 } else {
2229 r.endColumn = 0;
2230 r.endLine = 0;
2231 break;
2232 }
2233 }
2234
2235 return r;
2236}
2237
2238void NormalViMode::stickStickyColumnToEOL()
2239{
2240 if (m_keys.size() == 1) {
2241 m_stickyColumn = KateVi::EOL;
2242 }
2243}
2244
2245Range NormalViMode::motionToEOL()
2246{
2247 KTextEditor::Cursor c(m_view->cursorPosition());
2248
2249 stickStickyColumnToEOL();
2250
2251 unsigned int line = c.line() + (getCount() - 1);
2252 Range r(line, doc()->lineLength(line) - 1, InclusiveMotion);
2253
2254 return r;
2255}
2256Range NormalViMode::motionToLastNonBlank()
2257{
2258 KTextEditor::Cursor c(m_view->cursorPosition());
2259
2260 stickStickyColumnToEOL();
2261
2262 unsigned int line = c.line() + (getCount() - 1);
2263
2264 const auto text_line = doc()->plainKateTextLine(line);
2265 Range r(line, text_line.previousNonSpaceChar(text_line.length()), InclusiveMotion);
2266 return r;
2267}
2268
2269Range NormalViMode::motionToColumn0()
2270{
2271 m_stickyColumn = -1;
2272 KTextEditor::Cursor cursor(m_view->cursorPosition());
2273 Range r(cursor.line(), 0, ExclusiveMotion);
2274
2275 return r;
2276}
2277
2278Range NormalViMode::motionToFirstCharacterOfLine()
2279{
2280 m_stickyColumn = -1;
2281
2282 KTextEditor::Cursor cursor(m_view->cursorPosition());
2283 int c = getFirstNonBlank();
2284
2285 Range r(cursor.line(), c, ExclusiveMotion);
2286
2287 return r;
2288}
2289
2290Range NormalViMode::motionFindChar()
2291{
2292 m_lastTFcommand = m_keys;
2293 KTextEditor::Cursor cursor(m_view->cursorPosition());
2294 QString line = getLine();
2295
2296 m_stickyColumn = -1;
2297
2298 int matchColumn = cursor.column();
2299
2300 for (int i = 0; i < getCount(); i++) {
2301 matchColumn = line.indexOf(QStringView(m_keys).right(1), matchColumn + 1);
2302 if (matchColumn == -1) {
2303 break;
2304 }
2305 }
2306
2307 Range r;
2308
2309 if (matchColumn != -1) {
2310 r.endColumn = matchColumn;
2311 r.endLine = cursor.line();
2312 } else {
2313 return Range::invalid();
2314 }
2315
2316 return r;
2317}
2318
2319Range NormalViMode::motionFindCharBackward()
2320{
2321 m_lastTFcommand = m_keys;
2322 KTextEditor::Cursor cursor(m_view->cursorPosition());
2323 QString line = getLine();
2324
2325 m_stickyColumn = -1;
2326
2327 int matchColumn = -1;
2328
2329 int hits = 0;
2330 int i = cursor.column() - 1;
2331
2332 while (hits != getCount() && i >= 0) {
2333 if (line.at(i) == m_keys.at(m_keys.size() - 1)) {
2334 hits++;
2335 }
2336
2337 if (hits == getCount()) {
2338 matchColumn = i;
2339 }
2340
2341 i--;
2342 }
2343
2344 Range r(cursor, ExclusiveMotion);
2345
2346 if (matchColumn != -1) {
2347 r.endColumn = matchColumn;
2348 r.endLine = cursor.line();
2349 } else {
2350 return Range::invalid();
2351 }
2352
2353 return r;
2354}
2355
2356Range NormalViMode::motionToChar()
2357{
2358 m_lastTFcommand = m_keys;
2359 KTextEditor::Cursor cursor(m_view->cursorPosition());
2360 QString line = getLine();
2361
2362 m_stickyColumn = -1;
2363 Range r;
2364 r.endColumn = -1;
2365 r.endLine = -1;
2366
2367 int matchColumn = cursor.column() + (m_isRepeatedTFcommand ? 2 : 1);
2368
2369 for (int i = 0; i < getCount(); i++) {
2370 const int lastColumn = matchColumn;
2371 matchColumn = line.indexOf(m_keys.right(1), matchColumn + ((i > 0) ? 1 : 0));
2372 if (matchColumn == -1) {
2373 if (m_isRepeatedTFcommand) {
2374 matchColumn = lastColumn;
2375 } else {
2376 return Range::invalid();
2377 }
2378 break;
2379 }
2380 }
2381
2382 r.endColumn = matchColumn - 1;
2383 r.endLine = cursor.line();
2384
2385 m_isRepeatedTFcommand = false;
2386 return r;
2387}
2388
2389Range NormalViMode::motionToCharBackward()
2390{
2391 m_lastTFcommand = m_keys;
2392 KTextEditor::Cursor cursor(m_view->cursorPosition());
2393 QString line = getLine();
2394
2395 const int originalColumn = cursor.column();
2396 m_stickyColumn = -1;
2397
2398 int matchColumn = originalColumn - 1;
2399
2400 int hits = 0;
2401 int i = cursor.column() - (m_isRepeatedTFcommand ? 2 : 1);
2402
2403 Range r(cursor, ExclusiveMotion);
2404
2405 while (hits != getCount() && i >= 0) {
2406 if (line.at(i) == m_keys.at(m_keys.size() - 1)) {
2407 hits++;
2408 }
2409
2410 if (hits == getCount()) {
2411 matchColumn = i;
2412 }
2413
2414 i--;
2415 }
2416
2417 if (hits == getCount()) {
2418 r.endColumn = matchColumn + 1;
2419 r.endLine = cursor.line();
2420 } else {
2421 r.valid = false;
2422 }
2423
2424 m_isRepeatedTFcommand = false;
2425
2426 return r;
2427}
2428
2429Range NormalViMode::motionRepeatlastTF()
2430{
2431 if (!m_lastTFcommand.isEmpty()) {
2432 m_isRepeatedTFcommand = true;
2433 m_keys = m_lastTFcommand;
2434 if (m_keys.at(0) == QLatin1Char('f')) {
2435 return motionFindChar();
2436 } else if (m_keys.at(0) == QLatin1Char('F')) {
2437 return motionFindCharBackward();
2438 } else if (m_keys.at(0) == QLatin1Char('t')) {
2439 return motionToChar();
2440 } else if (m_keys.at(0) == QLatin1Char('T')) {
2441 return motionToCharBackward();
2442 }
2443 }
2444
2445 // there was no previous t/f command
2446 return Range::invalid();
2447}
2448
2449Range NormalViMode::motionRepeatlastTFBackward()
2450{
2451 if (!m_lastTFcommand.isEmpty()) {
2452 m_isRepeatedTFcommand = true;
2453 m_keys = m_lastTFcommand;
2454 if (m_keys.at(0) == QLatin1Char('f')) {
2455 return motionFindCharBackward();
2456 } else if (m_keys.at(0) == QLatin1Char('F')) {
2457 return motionFindChar();
2458 } else if (m_keys.at(0) == QLatin1Char('t')) {
2459 return motionToCharBackward();
2460 } else if (m_keys.at(0) == QLatin1Char('T')) {
2461 return motionToChar();
2462 }
2463 }
2464
2465 // there was no previous t/f command
2466 return Range::invalid();
2467}
2468
2469Range NormalViMode::motionToLineFirst()
2470{
2471 Range r(getCount() - 1, 0, InclusiveMotion);
2472 m_stickyColumn = -1;
2473
2474 if (r.endLine > doc()->lines() - 1) {
2475 r.endLine = doc()->lines() - 1;
2476 }
2477 r.jump = true;
2478
2479 return r;
2480}
2481
2482Range NormalViMode::motionToLineLast()
2483{
2484 Range r(doc()->lines() - 1, 0, InclusiveMotion);
2485 m_stickyColumn = -1;
2486
2487 // don't use getCount() here, no count and a count of 1 is different here...
2488 if (m_count != 0) {
2489 r.endLine = m_count - 1;
2490 }
2491
2492 if (r.endLine > doc()->lines() - 1) {
2493 r.endLine = doc()->lines() - 1;
2494 }
2495 r.jump = true;
2496
2497 return r;
2498}
2499
2500Range NormalViMode::motionToScreenColumn()
2501{
2502 m_stickyColumn = -1;
2503
2504 KTextEditor::Cursor c(m_view->cursorPosition());
2505
2506 int column = getCount() - 1;
2507
2508 if (doc()->lineLength(c.line()) - 1 < (int)getCount() - 1) {
2509 column = doc()->lineLength(c.line()) - 1;
2510 }
2511
2512 return Range(c.line(), column, ExclusiveMotion);
2513}
2514
2515Range NormalViMode::motionToMark()
2516{
2517 Range r;
2518
2519 m_stickyColumn = -1;
2520
2521 QChar reg = m_keys.at(m_keys.size() - 1);
2522
2523 KTextEditor::Cursor c = m_viInputModeManager->marks()->getMarkPosition(reg);
2524 if (c.isValid()) {
2525 r.endLine = c.line();
2526 r.endColumn = c.column();
2527 } else {
2528 error(i18n("Mark not set: %1", m_keys.right(1)));
2529 r.valid = false;
2530 }
2531
2532 r.jump = true;
2533
2534 return r;
2535}
2536
2537Range NormalViMode::motionToMarkLine()
2538{
2539 Range r = motionToMark();
2540 r.endColumn = getFirstNonBlank(r.endLine);
2541 r.jump = true;
2542 m_stickyColumn = -1;
2543 return r;
2544}
2545
2546Range NormalViMode::motionToMatchingItem()
2547{
2548 Range r;
2549 int lines = doc()->lines();
2550
2551 // If counted, then it's not a motion to matching item anymore,
2552 // but a motion to the N'th percentage of the document
2553 if (isCounted()) {
2554 int count = getCount();
2555 if (count > 100) {
2556 return r;
2557 }
2558 r.endLine = qRound(lines * count / 100.0) - 1;
2559 r.endColumn = 0;
2560 return r;
2561 }
2562
2563 KTextEditor::Cursor c(m_view->cursorPosition());
2564
2565 QString l = getLine();
2566 int n1 = l.indexOf(m_matchItemRegex, c.column());
2567
2568 m_stickyColumn = -1;
2569
2570 if (n1 < 0) {
2571 return Range::invalid();
2572 }
2573
2574 const auto bracketChar = l.at(n1);
2575 // use Kate's built-in matching bracket finder for brackets
2576 if (bracketChar == QLatin1Char('(') || bracketChar == QLatin1Char(')') || bracketChar == QLatin1Char('{') || bracketChar == QLatin1Char('}')
2577 || bracketChar == QLatin1Char('[') || bracketChar == QLatin1Char(']')) {
2578 // findMatchingBracket requires us to move the cursor to the
2579 // first bracket, but we don't want the cursor to really move
2580 // in case this is e.g. a yank, so restore it to its original
2581 // position afterwards.
2582 c.setColumn(n1);
2583 const KTextEditor::Cursor oldCursorPos = m_view->cursorPosition();
2584 updateCursor(c);
2585
2586 // find the matching one
2587 c = m_viewInternal->findMatchingBracket();
2588 if (c > m_view->cursorPosition()) {
2589 c.setColumn(c.column() - 1);
2590 }
2591 m_view->setCursorPosition(oldCursorPos);
2592 } else {
2593 // text item we want to find a matching item for
2594 static const QRegularExpression boundaryRegex(QStringLiteral("\\b|\\s|$"), QRegularExpression::UseUnicodePropertiesOption);
2595 const int n2 = l.indexOf(boundaryRegex, n1);
2596 QString item = l.mid(n1, n2 - n1);
2597 QString matchingItem = m_matchingItems[item];
2598
2599 int toFind = 1;
2600 int line = c.line();
2601 int column = n2 - item.length();
2602 bool reverse = false;
2603
2604 if (matchingItem.startsWith(QLatin1Char('-'))) {
2605 matchingItem.remove(0, 1); // remove the '-'
2606 reverse = true;
2607 }
2608
2609 // make sure we don't hit the text item we started the search from
2610 if (column == 0 && reverse) {
2611 column -= item.length();
2612 }
2613
2614 int itemIdx;
2615 int matchItemIdx;
2616
2617 while (toFind > 0) {
2618 if (reverse) {
2619 itemIdx = l.lastIndexOf(item, column - 1);
2620 matchItemIdx = l.lastIndexOf(matchingItem, column - 1);
2621
2622 if (itemIdx != -1 && (matchItemIdx == -1 || itemIdx > matchItemIdx)) {
2623 ++toFind;
2624 }
2625 } else {
2626 itemIdx = l.indexOf(item, column);
2627 matchItemIdx = l.indexOf(matchingItem, column);
2628
2629 if (itemIdx != -1 && (matchItemIdx == -1 || itemIdx < matchItemIdx)) {
2630 ++toFind;
2631 }
2632 }
2633
2634 if (matchItemIdx != -1 || itemIdx != -1) {
2635 if (!reverse) {
2636 column = qMin((unsigned int)itemIdx, (unsigned int)matchItemIdx);
2637 } else {
2638 column = qMax(itemIdx, matchItemIdx);
2639 }
2640 }
2641
2642 if (matchItemIdx != -1) { // match on current line
2643 if (matchItemIdx == column) {
2644 --toFind;
2645 c.setLine(line);
2646 c.setColumn(column);
2647 }
2648 } else { // no match, advance one line if possible
2649 (reverse) ? --line : ++line;
2650 column = 0;
2651
2652 if ((!reverse && line >= lines) || (reverse && line < 0)) {
2653 r.valid = false;
2654 break;
2655 } else {
2656 l = getLine(line);
2657 }
2658 }
2659 }
2660 }
2661
2662 r.endLine = c.line();
2663 r.endColumn = c.column();
2664 r.jump = true;
2665
2666 return r;
2667}
2668
2669Range NormalViMode::motionToNextBraceBlockStart()
2670{
2671 Range r;
2672
2673 m_stickyColumn = -1;
2674
2675 int line = findLineStartingWitchChar(QLatin1Char('{'), getCount());
2676
2677 if (line == -1) {
2678 return Range::invalid();
2679 }
2680
2681 r.endLine = line;
2682 r.endColumn = 0;
2683 r.jump = true;
2684
2685 if (motionWillBeUsedWithCommand()) {
2686 // Delete from cursor (inclusive) to the '{' (exclusive).
2687 // If we are on the first column, then delete the entire current line.
2688 r.motionType = ExclusiveMotion;
2689 if (m_view->cursorPosition().column() != 0) {
2690 r.endLine--;
2691 r.endColumn = doc()->lineLength(r.endLine);
2692 }
2693 }
2694
2695 return r;
2696}
2697
2698Range NormalViMode::motionToPreviousBraceBlockStart()
2699{
2700 Range r;
2701
2702 m_stickyColumn = -1;
2703
2704 int line = findLineStartingWitchChar(QLatin1Char('{'), getCount(), false);
2705
2706 if (line == -1) {
2707 return Range::invalid();
2708 }
2709
2710 r.endLine = line;
2711 r.endColumn = 0;
2712 r.jump = true;
2713
2714 if (motionWillBeUsedWithCommand()) {
2715 // With a command, do not include the { or the cursor position.
2716 r.motionType = ExclusiveMotion;
2717 }
2718
2719 return r;
2720}
2721
2722Range NormalViMode::motionToNextBraceBlockEnd()
2723{
2724 Range r;
2725
2726 m_stickyColumn = -1;
2727
2728 int line = findLineStartingWitchChar(QLatin1Char('}'), getCount());
2729
2730 if (line == -1) {
2731 return Range::invalid();
2732 }
2733
2734 r.endLine = line;
2735 r.endColumn = 0;
2736 r.jump = true;
2737
2738 if (motionWillBeUsedWithCommand()) {
2739 // Delete from cursor (inclusive) to the '}' (exclusive).
2740 // If we are on the first column, then delete the entire current line.
2741 r.motionType = ExclusiveMotion;
2742 if (m_view->cursorPosition().column() != 0) {
2743 r.endLine--;
2744 r.endColumn = doc()->lineLength(r.endLine);
2745 }
2746 }
2747
2748 return r;
2749}
2750
2751Range NormalViMode::motionToPreviousBraceBlockEnd()
2752{
2753 Range r;
2754
2755 m_stickyColumn = -1;
2756
2757 int line = findLineStartingWitchChar(QLatin1Char('}'), getCount(), false);
2758
2759 if (line == -1) {
2760 return Range::invalid();
2761 }
2762
2763 r.endLine = line;
2764 r.endColumn = 0;
2765 r.jump = true;
2766
2767 if (motionWillBeUsedWithCommand()) {
2768 r.motionType = ExclusiveMotion;
2769 }
2770
2771 return r;
2772}
2773
2774Range NormalViMode::motionToNextOccurrence()
2775{
2776 const QString word = getWordUnderCursor();
2777 Searcher *searcher = m_viInputModeManager->searcher();
2778 const Range match = searcher->findWordForMotion(word, false, getWordRangeUnderCursor().start(), getCount());
2779 if (searcher->lastSearchWrapped()) {
2780 m_view->showSearchWrappedHint(/*isReverseSearch*/ false);
2781 }
2782
2783 return Range(match.startLine, match.startColumn, ExclusiveMotion);
2784}
2785
2786Range NormalViMode::motionToPrevOccurrence()
2787{
2788 const QString word = getWordUnderCursor();
2789 Searcher *searcher = m_viInputModeManager->searcher();
2790 const Range match = searcher->findWordForMotion(word, true, getWordRangeUnderCursor().start(), getCount());
2791 if (searcher->lastSearchWrapped()) {
2792 m_view->showSearchWrappedHint(/*isReverseSearch*/ true);
2793 }
2794
2795 return Range(match.startLine, match.startColumn, ExclusiveMotion);
2796}
2797
2798Range NormalViMode::motionToFirstLineOfWindow()
2799{
2800 int lines_to_go;
2801 if (linesDisplayed() <= (unsigned int)m_viewInternal->endLine()) {
2802 lines_to_go = m_viewInternal->endLine() - linesDisplayed() - m_view->cursorPosition().line() + 1;
2803 } else {
2804 lines_to_go = -m_view->cursorPosition().line();
2805 }
2806
2807 Range r = goLineUpDown(lines_to_go);
2808 r.endColumn = getFirstNonBlank(r.endLine);
2809 return r;
2810}
2811
2812Range NormalViMode::motionToMiddleLineOfWindow()
2813{
2814 int lines_to_go;
2815 if (linesDisplayed() <= (unsigned int)m_viewInternal->endLine()) {
2816 lines_to_go = m_viewInternal->endLine() - linesDisplayed() / 2 - m_view->cursorPosition().line();
2817 } else {
2818 lines_to_go = m_viewInternal->endLine() / 2 - m_view->cursorPosition().line();
2819 }
2820
2821 Range r = goLineUpDown(lines_to_go);
2822 r.endColumn = getFirstNonBlank(r.endLine);
2823 return r;
2824}
2825
2826Range NormalViMode::motionToLastLineOfWindow()
2827{
2828 int lines_to_go;
2829 if (linesDisplayed() <= (unsigned int)m_viewInternal->endLine()) {
2830 lines_to_go = m_viewInternal->endLine() - m_view->cursorPosition().line();
2831 } else {
2832 lines_to_go = m_viewInternal->endLine() - m_view->cursorPosition().line();
2833 }
2834
2835 Range r = goLineUpDown(lines_to_go);
2836 r.endColumn = getFirstNonBlank(r.endLine);
2837 return r;
2838}
2839
2840Range NormalViMode::motionToNextVisualLine()
2841{
2842 return goVisualLineUpDown(getCount());
2843}
2844
2845Range NormalViMode::motionToPrevVisualLine()
2846{
2847 return goVisualLineUpDown(-getCount());
2848}
2849
2850Range NormalViMode::motionToPreviousSentence()
2851{
2852 KTextEditor::Cursor c = findSentenceStart();
2853 int linenum = c.line();
2854 int column = c.column();
2855 const bool skipSpaces = doc()->line(linenum).isEmpty();
2856
2857 if (skipSpaces) {
2858 linenum--;
2859 if (linenum >= 0) {
2860 column = doc()->line(linenum).size() - 1;
2861 }
2862 }
2863
2864 for (int i = linenum; i >= 0; i--) {
2865 const QString &line = doc()->line(i);
2866
2867 if (line.isEmpty() && !skipSpaces) {
2868 return Range(i, 0, InclusiveMotion);
2869 }
2870
2871 if (column < 0 && !line.isEmpty()) {
2872 column = line.size() - 1;
2873 }
2874
2875 for (int j = column; j >= 0; j--) {
2876 if (skipSpaces || QStringLiteral(".?!").indexOf(line.at(j)) != -1) {
2877 c.setLine(i);
2878 c.setColumn(j);
2879 updateCursor(c);
2880 c = findSentenceStart();
2881 return Range(c, InclusiveMotion);
2882 }
2883 }
2884 column = line.size() - 1;
2885 }
2886 return Range(0, 0, InclusiveMotion);
2887}
2888
2889Range NormalViMode::motionToNextSentence()
2890{
2891 KTextEditor::Cursor c = findSentenceEnd();
2892 int linenum = c.line();
2893 int column = c.column() + 1;
2894 const bool skipSpaces = doc()->line(linenum).isEmpty();
2895
2896 for (int i = linenum; i < doc()->lines(); i++) {
2897 const QString &line = doc()->line(i);
2898
2899 if (line.isEmpty() && !skipSpaces) {
2900 return Range(i, 0, InclusiveMotion);
2901 }
2902
2903 for (int j = column; j < line.size(); j++) {
2904 if (!line.at(j).isSpace()) {
2905 return Range(i, j, InclusiveMotion);
2906 }
2907 }
2908 column = 0;
2909 }
2910
2911 c = doc()->documentEnd();
2912 return Range(c, InclusiveMotion);
2913}
2914
2915Range NormalViMode::motionToBeforeParagraph()
2916{
2917 KTextEditor::Cursor c(m_view->cursorPosition());
2918
2919 int line = c.line();
2920
2921 m_stickyColumn = -1;
2922
2923 for (int i = 0; i < getCount(); i++) {
2924 // advance at least one line, but if there are consecutive blank lines
2925 // skip them all
2926 do {
2927 line--;
2928 } while (line >= 0 && getLine(line + 1).length() == 0);
2929 while (line > 0 && getLine(line).length() != 0) {
2930 line--;
2931 }
2932 }
2933
2934 if (line < 0) {
2935 line = 0;
2936 }
2937
2938 Range r(line, 0, InclusiveMotion);
2939
2940 return r;
2941}
2942
2943Range NormalViMode::motionToAfterParagraph()
2944{
2945 KTextEditor::Cursor c(m_view->cursorPosition());
2946
2947 int line = c.line();
2948
2949 m_stickyColumn = -1;
2950
2951 for (int i = 0; i < getCount(); i++) {
2952 // advance at least one line, but if there are consecutive blank lines
2953 // skip them all
2954 do {
2955 line++;
2956 } while (line <= doc()->lines() - 1 && getLine(line - 1).length() == 0);
2957 while (line < doc()->lines() - 1 && getLine(line).length() != 0) {
2958 line++;
2959 }
2960 }
2961
2962 if (line >= doc()->lines()) {
2963 line = doc()->lines() - 1;
2964 }
2965
2966 // if we ended up on the last line, the cursor should be placed on the last column
2967 int column = (line == doc()->lines() - 1) ? qMax(getLine(line).length() - 1, 0) : 0;
2968
2969 return Range(line, column, InclusiveMotion);
2970}
2971
2972Range NormalViMode::motionToIncrementalSearchMatch()
2973{
2974 return Range(m_positionWhenIncrementalSearchBegan.line(),
2975 m_positionWhenIncrementalSearchBegan.column(),
2976 m_view->cursorPosition().line(),
2977 m_view->cursorPosition().column(),
2978 ExclusiveMotion);
2979}
2980
2981////////////////////////////////////////////////////////////////////////////////
2982// TEXT OBJECTS
2983////////////////////////////////////////////////////////////////////////////////
2984
2985Range NormalViMode::textObjectAWord()
2986{
2987 KTextEditor::Cursor c(m_view->cursorPosition());
2988
2989 KTextEditor::Cursor c1 = c;
2990
2991 bool startedOnSpace = false;
2992 if (doc()->characterAt(c).isSpace()) {
2993 startedOnSpace = true;
2994 } else {
2995 c1 = findPrevWordStart(c.line(), c.column() + 1, true);
2996 if (!c1.isValid()) {
2997 c1 = KTextEditor::Cursor(0, 0);
2998 }
2999 }
3001 for (int i = 1; i <= getCount(); i++) {
3002 c2 = findWordEnd(c2.line(), c2.column());
3003 }
3004 if (!c1.isValid() || !c2.isValid()) {
3005 return Range::invalid();
3006 }
3007 // Adhere to some of Vim's bizarre rules of whether to swallow ensuing spaces or not.
3008 // Don't ask ;)
3009 const KTextEditor::Cursor nextWordStart = findNextWordStart(c2.line(), c2.column());
3010 if (nextWordStart.isValid() && nextWordStart.line() == c2.line()) {
3011 if (!startedOnSpace) {
3012 c2 = KTextEditor::Cursor(nextWordStart.line(), nextWordStart.column() - 1);
3013 }
3014 } else {
3015 c2 = KTextEditor::Cursor(c2.line(), doc()->lineLength(c2.line()) - 1);
3016 }
3017 bool swallowCarriageReturnAtEndOfLine = false;
3018 if (c2.line() != c.line() && c2.column() == doc()->lineLength(c2.line()) - 1) {
3019 // Greedily descend to the next line, so as to swallow the carriage return on this line.
3020 c2 = KTextEditor::Cursor(c2.line() + 1, 0);
3021 swallowCarriageReturnAtEndOfLine = true;
3022 }
3023 const bool swallowPrecedingSpaces =
3024 (c2.column() == doc()->lineLength(c2.line()) - 1 && !doc()->characterAt(c2).isSpace()) || startedOnSpace || swallowCarriageReturnAtEndOfLine;
3025 if (swallowPrecedingSpaces) {
3026 if (c1.column() != 0) {
3027 const KTextEditor::Cursor previousNonSpace = findPrevWordEnd(c.line(), c.column());
3028 if (previousNonSpace.isValid() && previousNonSpace.line() == c1.line()) {
3029 c1 = KTextEditor::Cursor(previousNonSpace.line(), previousNonSpace.column() + 1);
3030 } else if (startedOnSpace || swallowCarriageReturnAtEndOfLine) {
3031 c1 = KTextEditor::Cursor(c1.line(), 0);
3032 }
3033 }
3034 }
3035
3036 return Range(c1, c2, !swallowCarriageReturnAtEndOfLine ? InclusiveMotion : ExclusiveMotion);
3037}
3038
3039Range NormalViMode::textObjectInnerWord()
3040{
3041 KTextEditor::Cursor c(m_view->cursorPosition());
3042
3043 KTextEditor::Cursor c1 = findPrevWordStart(c.line(), c.column() + 1, true);
3044 if (!c1.isValid()) {
3045 c1 = KTextEditor::Cursor(0, 0);
3046 }
3047 // need to start search in column-1 because it might be a one-character word
3048 KTextEditor::Cursor c2(c.line(), c.column() - 1);
3049
3050 for (int i = 0; i < getCount(); i++) {
3051 c2 = findWordEnd(c2.line(), c2.column(), true);
3052 }
3053
3054 if (!c2.isValid()) {
3055 c2 = doc()->documentEnd();
3056 }
3057
3058 // sanity check
3059 if (c1.line() != c2.line() || c1.column() > c2.column()) {
3060 return Range::invalid();
3061 }
3062 return Range(c1, c2, InclusiveMotion);
3063}
3064
3065Range NormalViMode::textObjectAWORD()
3066{
3067 KTextEditor::Cursor c(m_view->cursorPosition());
3068
3069 KTextEditor::Cursor c1 = c;
3070
3071 bool startedOnSpace = false;
3072 if (doc()->characterAt(c).isSpace()) {
3073 startedOnSpace = true;
3074 } else {
3075 c1 = findPrevWORDStart(c.line(), c.column() + 1, true);
3076 if (!c1.isValid()) {
3077 c1 = KTextEditor::Cursor(0, 0);
3078 }
3079 }
3081 for (int i = 1; i <= getCount(); i++) {
3082 c2 = findWORDEnd(c2.line(), c2.column());
3083 }
3084 if (!c1.isValid() || !c2.isValid()) {
3085 return Range::invalid();
3086 }
3087 // Adhere to some of Vim's bizarre rules of whether to swallow ensuing spaces or not.
3088 // Don't ask ;)
3089 const KTextEditor::Cursor nextWordStart = findNextWordStart(c2.line(), c2.column());
3090 if (nextWordStart.isValid() && nextWordStart.line() == c2.line()) {
3091 if (!startedOnSpace) {
3092 c2 = KTextEditor::Cursor(nextWordStart.line(), nextWordStart.column() - 1);
3093 }
3094 } else {
3095 c2 = KTextEditor::Cursor(c2.line(), doc()->lineLength(c2.line()) - 1);
3096 }
3097 bool swallowCarriageReturnAtEndOfLine = false;
3098 if (c2.line() != c.line() && c2.column() == doc()->lineLength(c2.line()) - 1) {
3099 // Greedily descend to the next line, so as to swallow the carriage return on this line.
3100 c2 = KTextEditor::Cursor(c2.line() + 1, 0);
3101 swallowCarriageReturnAtEndOfLine = true;
3102 }
3103 const bool swallowPrecedingSpaces =
3104 (c2.column() == doc()->lineLength(c2.line()) - 1 && !doc()->characterAt(c2).isSpace()) || startedOnSpace || swallowCarriageReturnAtEndOfLine;
3105 if (swallowPrecedingSpaces) {
3106 if (c1.column() != 0) {
3107 const KTextEditor::Cursor previousNonSpace = findPrevWORDEnd(c.line(), c.column());
3108 if (previousNonSpace.isValid() && previousNonSpace.line() == c1.line()) {
3109 c1 = KTextEditor::Cursor(previousNonSpace.line(), previousNonSpace.column() + 1);
3110 } else if (startedOnSpace || swallowCarriageReturnAtEndOfLine) {
3111 c1 = KTextEditor::Cursor(c1.line(), 0);
3112 }
3113 }
3114 }
3115
3116 return Range(c1, c2, !swallowCarriageReturnAtEndOfLine ? InclusiveMotion : ExclusiveMotion);
3117}
3118
3119Range NormalViMode::textObjectInnerWORD()
3120{
3121 KTextEditor::Cursor c(m_view->cursorPosition());
3122
3123 KTextEditor::Cursor c1 = findPrevWORDStart(c.line(), c.column() + 1, true);
3124 if (!c1.isValid()) {
3125 c1 = KTextEditor::Cursor(0, 0);
3126 }
3127 KTextEditor::Cursor c2(c);
3128
3129 for (int i = 0; i < getCount(); i++) {
3130 c2 = findWORDEnd(c2.line(), c2.column(), true);
3131 }
3132
3133 if (!c2.isValid()) {
3134 c2 = doc()->documentEnd();
3135 }
3136
3137 // sanity check
3138 if (c1.line() != c2.line() || c1.column() > c2.column()) {
3139 return Range::invalid();
3140 }
3141 return Range(c1, c2, InclusiveMotion);
3142}
3143
3144KTextEditor::Cursor NormalViMode::findSentenceStart()
3145{
3146 KTextEditor::Cursor c(m_view->cursorPosition());
3147 int linenum = c.line();
3148 int column = c.column();
3149 int prev = column;
3150
3151 for (int i = linenum; i >= 0; i--) {
3152 const QString &line = doc()->line(i);
3153 const int lineLength = line.size();
3154 if (i != linenum) {
3155 column = lineLength;
3156 }
3157
3158 // An empty line is the end of a paragraph.
3159 if (line.isEmpty()) {
3160 return KTextEditor::Cursor((i != linenum) ? i + 1 : i, prev);
3161 }
3162
3163 prev = column;
3164 for (int j = column; j >= 0; j--) {
3165 if (j == lineLength || line.at(j).isSpace()) {
3166 int lastSpace = j--;
3167 for (; j >= 0 && QStringLiteral("\"')]").indexOf(line.at(j)) != -1; j--) {
3168 ;
3169 }
3170
3171 if (j >= 0 && QStringLiteral(".!?").indexOf(line.at(j)) != -1) {
3172 if (lastSpace == lineLength) {
3173 // If the line ends with one of .!?, then the sentence starts from the next line.
3174 return KTextEditor::Cursor(i + 1, 0);
3175 }
3176
3177 return KTextEditor::Cursor(i, prev);
3178 }
3179 j = lastSpace;
3180 } else {
3181 prev = j;
3182 }
3183 }
3184 }
3185
3186 return KTextEditor::Cursor(0, 0);
3187}
3188
3189KTextEditor::Cursor NormalViMode::findSentenceEnd()
3190{
3191 KTextEditor::Cursor c(m_view->cursorPosition());
3192 int linenum = c.line();
3193 int column = c.column();
3194 int j = 0;
3195 int prev = 0;
3196
3197 for (int i = linenum; i < doc()->lines(); i++) {
3198 const QString &line = doc()->line(i);
3199
3200 // An empty line is the end of a paragraph.
3201 if (line.isEmpty()) {
3202 return KTextEditor::Cursor(linenum, j);
3203 }
3204
3205 // Iterating over the line to reach any '.', '!', '?'
3206 for (j = column; j < line.size(); j++) {
3207 if (QStringLiteral(".!?").indexOf(line.at(j)) != -1) {
3208 prev = j++;
3209 // Skip possible closing characters.
3210 for (; j < line.size() && QStringLiteral("\"')]").indexOf(line.at(j)) != -1; j++) {
3211 ;
3212 }
3213
3214 if (j >= line.size()) {
3215 return KTextEditor::Cursor(i, j - 1);
3216 }
3217
3218 // And hopefully we're done...
3219 if (line.at(j).isSpace()) {
3220 return KTextEditor::Cursor(i, j - 1);
3221 }
3222 j = prev;
3223 }
3224 }
3225 linenum = i;
3226 prev = column;
3227 column = 0;
3228 }
3229
3230 return KTextEditor::Cursor(linenum, j - 1);
3231}
3232
3233KTextEditor::Cursor NormalViMode::findParagraphStart()
3234{
3235 KTextEditor::Cursor c(m_view->cursorPosition());
3236 const bool firstBlank = doc()->line(c.line()).isEmpty();
3237 int prev = c.line();
3238
3239 for (int i = prev; i >= 0; i--) {
3240 if (doc()->line(i).isEmpty()) {
3241 if (i != prev) {
3242 prev = i + 1;
3243 }
3244
3245 /* Skip consecutive empty lines. */
3246 if (firstBlank) {
3247 i--;
3248 for (; i >= 0 && doc()->line(i).isEmpty(); i--, prev--) {
3249 ;
3250 }
3251 }
3252 return KTextEditor::Cursor(prev, 0);
3253 }
3254 }
3255 return KTextEditor::Cursor(0, 0);
3256}
3257
3258KTextEditor::Cursor NormalViMode::findParagraphEnd()
3259{
3260 KTextEditor::Cursor c(m_view->cursorPosition());
3261 int prev = c.line();
3262 int lines = doc()->lines();
3263 const bool firstBlank = doc()->line(prev).isEmpty();
3264
3265 for (int i = prev; i < lines; i++) {
3266 if (doc()->line(i).isEmpty()) {
3267 if (i != prev) {
3268 prev = i - 1;
3269 }
3270
3271 /* Skip consecutive empty lines. */
3272 if (firstBlank) {
3273 i++;
3274 for (; i < lines && doc()->line(i).isEmpty(); i++, prev++) {
3275 ;
3276 }
3277 }
3278 int length = doc()->lineLength(prev);
3279 return KTextEditor::Cursor(prev, (length <= 0) ? 0 : length - 1);
3280 }
3281 }
3282 return doc()->documentEnd();
3283}
3284
3285Range NormalViMode::textObjectInnerSentence()
3286{
3287 Range r;
3288 KTextEditor::Cursor c1 = findSentenceStart();
3289 KTextEditor::Cursor c2 = findSentenceEnd();
3290 updateCursor(c1);
3291
3292 r.startLine = c1.line();
3293 r.startColumn = c1.column();
3294 r.endLine = c2.line();
3295 r.endColumn = c2.column();
3296 return r;
3297}
3298
3299Range NormalViMode::textObjectASentence()
3300{
3301 int i;
3302 Range r = textObjectInnerSentence();
3303 const QString &line = doc()->line(r.endLine);
3304
3305 // Skip whitespaces and tabs.
3306 for (i = r.endColumn + 1; i < line.size(); i++) {
3307 if (!line.at(i).isSpace()) {
3308 break;
3309 }
3310 }
3311 r.endColumn = i - 1;
3312
3313 // Remove preceding spaces.
3314 if (r.startColumn != 0) {
3315 if (r.endColumn == line.size() - 1 && !line.at(r.endColumn).isSpace()) {
3316 const QString &line = doc()->line(r.startLine);
3317 for (i = r.startColumn - 1; i >= 0; i--) {
3318 if (!line.at(i).isSpace()) {
3319 break;
3320 }
3321 }
3322 r.startColumn = i + 1;
3323 }
3324 }
3325 return r;
3326}
3327
3328Range NormalViMode::textObjectInnerParagraph()
3329{
3330 Range r;
3331 KTextEditor::Cursor c1 = findParagraphStart();
3332 KTextEditor::Cursor c2 = findParagraphEnd();
3333 updateCursor(c1);
3334
3335 r.startLine = c1.line();
3336 r.startColumn = c1.column();
3337 r.endLine = c2.line();
3338 r.endColumn = c2.column();
3339 return r;
3340}
3341
3342Range NormalViMode::textObjectAParagraph()
3343{
3344 Range r = textObjectInnerParagraph();
3345 int lines = doc()->lines();
3346
3347 if (r.endLine + 1 < lines) {
3348 // If the next line is empty, remove all subsequent empty lines.
3349 // Otherwise we'll grab the next paragraph.
3350 if (doc()->line(r.endLine + 1).isEmpty()) {
3351 for (int i = r.endLine + 1; i < lines && doc()->line(i).isEmpty(); i++) {
3352 r.endLine++;
3353 }
3354 r.endColumn = 0;
3355 } else {
3356 KTextEditor::Cursor prev = m_view->cursorPosition();
3357 KTextEditor::Cursor c(r.endLine + 1, 0);
3358 updateCursor(c);
3359 c = findParagraphEnd();
3360 updateCursor(prev);
3361 r.endLine = c.line();
3362 r.endColumn = c.column();
3363 }
3364 } else if (doc()->lineLength(r.startLine) > 0) {
3365 // We went too far, but maybe we can grab previous empty lines.
3366 for (int i = r.startLine - 1; i >= 0 && doc()->line(i).isEmpty(); i--) {
3367 r.startLine--;
3368 }
3369 r.startColumn = 0;
3370 updateCursor(KTextEditor::Cursor(r.startLine, r.startColumn));
3371 } else {
3372 // We went too far and we're on empty lines, do nothing.
3373 return Range::invalid();
3374 }
3375 return r;
3376}
3377
3378Range NormalViMode::textObjectAQuoteDouble()
3379{
3380 return findSurroundingQuotes(QLatin1Char('"'), false);
3381}
3382
3383Range NormalViMode::textObjectInnerQuoteDouble()
3384{
3385 return findSurroundingQuotes(QLatin1Char('"'), true);
3386}
3387
3388Range NormalViMode::textObjectAQuoteSingle()
3389{
3390 return findSurroundingQuotes(QLatin1Char('\''), false);
3391}
3392
3393Range NormalViMode::textObjectInnerQuoteSingle()
3394{
3395 return findSurroundingQuotes(QLatin1Char('\''), true);
3396}
3397
3398Range NormalViMode::textObjectABackQuote()
3399{
3400 return findSurroundingQuotes(QLatin1Char('`'), false);
3401}
3402
3403Range NormalViMode::textObjectInnerBackQuote()
3404{
3405 return findSurroundingQuotes(QLatin1Char('`'), true);
3406}
3407
3408Range NormalViMode::textObjectAParen()
3409{
3410 return findSurroundingBrackets(QLatin1Char('('), QLatin1Char(')'), false, QLatin1Char('('), QLatin1Char(')'));
3411}
3412
3413Range NormalViMode::textObjectInnerParen()
3414{
3415 return findSurroundingBrackets(QLatin1Char('('), QLatin1Char(')'), true, QLatin1Char('('), QLatin1Char(')'));
3416}
3417
3418Range NormalViMode::textObjectABracket()
3419{
3420 return findSurroundingBrackets(QLatin1Char('['), QLatin1Char(']'), false, QLatin1Char('['), QLatin1Char(']'));
3421}
3422
3423Range NormalViMode::textObjectInnerBracket()
3424{
3425 return findSurroundingBrackets(QLatin1Char('['), QLatin1Char(']'), true, QLatin1Char('['), QLatin1Char(']'));
3426}
3427
3428Range NormalViMode::textObjectACurlyBracket()
3429{
3430 return findSurroundingBrackets(QLatin1Char('{'), QLatin1Char('}'), false, QLatin1Char('{'), QLatin1Char('}'));
3431}
3432
3433Range NormalViMode::textObjectInnerCurlyBracket()
3434{
3435 const Range allBetweenCurlyBrackets = findSurroundingBrackets(QLatin1Char('{'), QLatin1Char('}'), true, QLatin1Char('{'), QLatin1Char('}'));
3436 // Emulate the behaviour of vim, which tries to leave the closing bracket on its own line
3437 // if it was originally on a line different to that of the opening bracket.
3438 Range innerCurlyBracket(allBetweenCurlyBrackets);
3439
3440 if (innerCurlyBracket.startLine != innerCurlyBracket.endLine) {
3441 const bool openingBraceIsLastCharOnLine = innerCurlyBracket.startColumn == doc()->line(innerCurlyBracket.startLine).length();
3442 const bool stuffToDeleteIsAllOnEndLine = openingBraceIsLastCharOnLine && innerCurlyBracket.endLine == innerCurlyBracket.startLine + 1;
3443 const QString textLeadingClosingBracket = doc()->line(innerCurlyBracket.endLine).mid(0, innerCurlyBracket.endColumn + 1);
3444 const bool closingBracketHasLeadingNonWhitespace = !textLeadingClosingBracket.trimmed().isEmpty();
3445 if (stuffToDeleteIsAllOnEndLine) {
3446 if (!closingBracketHasLeadingNonWhitespace) {
3447 // Nothing there to select - abort.
3448 return Range::invalid();
3449 } else {
3450 // Shift the beginning of the range to the start of the line containing the closing bracket.
3451 innerCurlyBracket.startLine++;
3452 innerCurlyBracket.startColumn = 0;
3453 }
3454 } else {
3455 if (openingBraceIsLastCharOnLine && !closingBracketHasLeadingNonWhitespace) {
3456 innerCurlyBracket.startLine++;
3457 innerCurlyBracket.startColumn = 0;
3458 m_lastMotionWasLinewiseInnerBlock = true;
3459 }
3460 {
3461 // The line containing the end bracket is left alone if the end bracket is preceded by just whitespace,
3462 // else we need to delete everything (i.e. end up with "{}")
3463 if (!closingBracketHasLeadingNonWhitespace) {
3464 // Shrink the endpoint of the range so that it ends at the end of the line above,
3465 // leaving the closing bracket on its own line.
3466 innerCurlyBracket.endLine--;
3467 innerCurlyBracket.endColumn = doc()->line(innerCurlyBracket.endLine).length();
3468 }
3469 }
3470 }
3471 }
3472 return innerCurlyBracket;
3473}
3474
3475Range NormalViMode::textObjectAInequalitySign()
3476{
3477 return findSurroundingBrackets(QLatin1Char('<'), QLatin1Char('>'), false, QLatin1Char('<'), QLatin1Char('>'));
3478}
3479
3480Range NormalViMode::textObjectInnerInequalitySign()
3481{
3482 return findSurroundingBrackets(QLatin1Char('<'), QLatin1Char('>'), true, QLatin1Char('<'), QLatin1Char('>'));
3483}
3484
3485Range NormalViMode::textObjectAComma()
3486{
3487 return textObjectComma(false);
3488}
3489
3490Range NormalViMode::textObjectInnerComma()
3491{
3492 return textObjectComma(true);
3493}
3494
3495QRegularExpression NormalViMode::generateMatchingItemRegex() const
3496{
3497 QString pattern(QStringLiteral("\\[|\\]|\\{|\\}|\\(|\\)|"));
3498
3499 for (QString s : std::as_const(m_matchingItems)) {
3500 if (s.startsWith(QLatin1Char('-'))) {
3501 s.remove(0, 1);
3502 }
3503 s.replace(QLatin1Char('*'), QStringLiteral("\\*"));
3504 s.replace(QLatin1Char('+'), QStringLiteral("\\+"));
3505 s.replace(QLatin1Char('['), QStringLiteral("\\["));
3506 s.replace(QLatin1Char(']'), QStringLiteral("\\]"));
3507 s.replace(QLatin1Char('('), QStringLiteral("\\("));
3508 s.replace(QLatin1Char(')'), QStringLiteral("\\)"));
3509 s.replace(QLatin1Char('{'), QStringLiteral("\\{"));
3510 s.replace(QLatin1Char('}'), QStringLiteral("\\}"));
3511
3512 s.append(QLatin1Char('|'));
3513 pattern.append(s);
3514 }
3515 // remove extra "|" at the end
3516 pattern.chop(1);
3517
3519}
3520
3521// returns the operation mode that should be used. this is decided by using the following heuristic:
3522// 1. if we're in visual block mode, it should be Block
3523// 2. if we're in visual line mode OR the range spans several lines, it should be LineWise
3524// 3. if neither of these is true, CharWise is returned
3525// 4. there are some motion that makes all operator charwise, if we have one of them mode will be CharWise
3526OperationMode NormalViMode::getOperationMode() const
3527{
3528 OperationMode m = CharWise;
3529
3530 if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualBlockMode) {
3531 m = Block;
3532 } else if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualLineMode
3533 || (m_commandRange.startLine != m_commandRange.endLine && m_viInputModeManager->getCurrentViMode() != ViMode::VisualMode)) {
3534 m = LineWise;
3535 }
3536
3537 if (m_commandWithMotion && !m_linewiseCommand) {
3538 m = CharWise;
3539 }
3540
3541 if (m_lastMotionWasLinewiseInnerBlock) {
3542 m = LineWise;
3543 }
3544
3545 return m;
3546}
3547
3548bool NormalViMode::paste(PasteLocation pasteLocation, bool isgPaste, bool isIndentedPaste)
3549{
3550 KTextEditor::Cursor pasteAt(m_view->cursorPosition());
3551 KTextEditor::Cursor cursorAfterPaste = pasteAt;
3552 QChar reg = getChosenRegister(UnnamedRegister);
3553
3554 OperationMode m = getRegisterFlag(reg);
3555 QString textToInsert = getRegisterContent(reg);
3556 const bool isTextMultiLine = textToInsert.count(QLatin1Char('\n')) > 0;
3557
3558 // In temporary normal mode, p/P act as gp/gP.
3559 isgPaste |= m_viInputModeManager->getTemporaryNormalMode();
3560
3561 if (textToInsert.isEmpty()) {
3562 error(i18n("Nothing in register %1", reg.toLower()));
3563 return false;
3564 }
3565
3566 if (getCount() > 1) {
3567 textToInsert = textToInsert.repeated(getCount()); // FIXME: does this make sense for blocks?
3568 }
3569
3570 if (m == LineWise) {
3571 pasteAt.setColumn(0);
3572 if (isIndentedPaste) {
3573 // Note that this does indeed work if there is no non-whitespace on the current line or if
3574 // the line is empty!
3575 static const QRegularExpression nonWhitespaceRegex(QStringLiteral("[^\\s]"));
3576 const QString pasteLineString = doc()->line(pasteAt.line());
3577 const QString leadingWhiteSpaceOnCurrentLine = pasteLineString.mid(0, pasteLineString.indexOf(nonWhitespaceRegex));
3578 const QString leadingWhiteSpaceOnFirstPastedLine = textToInsert.mid(0, textToInsert.indexOf(nonWhitespaceRegex));
3579 // QString has no "left trim" method, bizarrely.
3580 while (textToInsert[0].isSpace()) {
3581 textToInsert = textToInsert.mid(1);
3582 }
3583 textToInsert.prepend(leadingWhiteSpaceOnCurrentLine);
3584 // Remove the last \n, temporarily: we're going to alter the indentation of each pasted line
3585 // by doing a search and replace on '\n's, but don't want to alter this one.
3586 textToInsert.chop(1);
3587 textToInsert.replace(QLatin1Char('\n') + leadingWhiteSpaceOnFirstPastedLine, QLatin1Char('\n') + leadingWhiteSpaceOnCurrentLine);
3588 textToInsert.append(QLatin1Char('\n')); // Re-add the temporarily removed last '\n'.
3589 }
3590 if (pasteLocation == AfterCurrentPosition) {
3591 textToInsert.chop(1); // remove the last \n
3592 pasteAt.setColumn(doc()->lineLength(pasteAt.line())); // paste after the current line and ...
3593 textToInsert.prepend(QLatin1Char('\n')); // ... prepend a \n, so the text starts on a new line
3594
3595 cursorAfterPaste.setLine(cursorAfterPaste.line() + 1);
3596 }
3597 if (isgPaste) {
3598 cursorAfterPaste.setLine(cursorAfterPaste.line() + textToInsert.count(QLatin1Char('\n')));
3599 }
3600 } else {
3601 if (pasteLocation == AfterCurrentPosition) {
3602 // Move cursor forward one before we paste. The position after the paste must also
3603 // be updated accordingly.
3604 if (getLine(pasteAt.line()).length() > 0) {
3605 pasteAt.setColumn(pasteAt.column() + 1);
3606 }
3607 cursorAfterPaste = pasteAt;
3608 }
3609 const bool leaveCursorAtStartOfPaste = isTextMultiLine && !isgPaste;
3610 if (!leaveCursorAtStartOfPaste) {
3611 cursorAfterPaste = cursorPosAtEndOfPaste(pasteAt, textToInsert);
3612 if (!isgPaste) {
3613 cursorAfterPaste.setColumn(cursorAfterPaste.column() - 1);
3614 }
3615 }
3616 }
3617
3618 doc()->editStart();
3619 if (m_view->selection()) {
3620 pasteAt = m_view->selectionRange().start();
3621 doc()->removeText(m_view->selectionRange());
3622 }
3623 doc()->insertText(pasteAt, textToInsert, m == Block);
3624 doc()->editEnd();
3625
3626 if (cursorAfterPaste.line() >= doc()->lines()) {
3627 cursorAfterPaste.setLine(doc()->lines() - 1);
3628 }
3629 updateCursor(cursorAfterPaste);
3630
3631 return true;
3632}
3633
3634KTextEditor::Cursor NormalViMode::cursorPosAtEndOfPaste(const KTextEditor::Cursor pasteLocation, const QString &pastedText)
3635{
3636 KTextEditor::Cursor cAfter = pasteLocation;
3637 const int lineCount = pastedText.count(QLatin1Char('\n')) + 1;
3638 if (lineCount == 1) {
3639 cAfter.setColumn(cAfter.column() + pastedText.length());
3640 } else {
3641 const int lastLineLength = pastedText.size() - (pastedText.lastIndexOf(QLatin1Char('\n')) + 1);
3642 cAfter.setColumn(lastLineLength);
3643 cAfter.setLine(cAfter.line() + lineCount - 1);
3644 }
3645 return cAfter;
3646}
3647
3648void NormalViMode::joinLines(unsigned int from, unsigned int to) const
3649{
3650 // make sure we don't try to join lines past the document end
3651 if (to >= (unsigned int)(doc()->lines())) {
3652 to = doc()->lines() - 1;
3653 }
3654
3655 // joining one line is a no-op
3656 if (from == to) {
3657 return;
3658 }
3659
3660 doc()->joinLines(from, to);
3661}
3662
3663void NormalViMode::reformatLines(unsigned int from, unsigned int to) const
3664{
3665 // BUG #340550: Do not remove empty lines when reformatting
3666 KTextEditor::DocumentPrivate *document = doc();
3667 auto isNonEmptyLine = [](QStringView text) {
3668 for (int i = 0; i < text.length(); ++i) {
3669 if (!text.at(i).isSpace()) {
3670 return true;
3671 }
3672 }
3673
3674 return false;
3675 };
3676 for (; from < to; ++from) {
3677 if (isNonEmptyLine(document->line(from))) {
3678 break;
3679 }
3680 }
3681 for (; to > from; --to) {
3682 if (isNonEmptyLine(document->line(to))) {
3683 break;
3684 }
3685 }
3686
3687 document->editStart();
3688 joinLines(from, to);
3689 document->wrapText(from, to);
3690 document->editEnd();
3691}
3692
3694{
3695 if (line < 0) {
3696 line = m_view->cursorPosition().line();
3697 }
3698
3699 // doc()->plainKateTextLine returns NULL if the line is out of bounds.
3700 Kate::TextLine l = doc()->plainKateTextLine(line);
3701 int c = l.firstChar();
3702 return (c < 0) ? 0 : c;
3703}
3704
3705// Tries to shrinks toShrink so that it fits tightly around rangeToShrinkTo.
3706void NormalViMode::shrinkRangeAroundCursor(Range &toShrink, const Range &rangeToShrinkTo) const
3707{
3708 if (!toShrink.valid || !rangeToShrinkTo.valid) {
3709 return;
3710 }
3711 KTextEditor::Cursor cursorPos = m_view->cursorPosition();
3712 if (rangeToShrinkTo.startLine >= cursorPos.line()) {
3713 if (rangeToShrinkTo.startLine > cursorPos.line()) {
3714 // Does not surround cursor; aborting.
3715 return;
3716 }
3717 Q_ASSERT(rangeToShrinkTo.startLine == cursorPos.line());
3718 if (rangeToShrinkTo.startColumn > cursorPos.column()) {
3719 // Does not surround cursor; aborting.
3720 return;
3721 }
3722 }
3723 if (rangeToShrinkTo.endLine <= cursorPos.line()) {
3724 if (rangeToShrinkTo.endLine < cursorPos.line()) {
3725 // Does not surround cursor; aborting.
3726 return;
3727 }
3728 Q_ASSERT(rangeToShrinkTo.endLine == cursorPos.line());
3729 if (rangeToShrinkTo.endColumn < cursorPos.column()) {
3730 // Does not surround cursor; aborting.
3731 return;
3732 }
3733 }
3734
3735 if (toShrink.startLine <= rangeToShrinkTo.startLine) {
3736 if (toShrink.startLine < rangeToShrinkTo.startLine) {
3737 toShrink.startLine = rangeToShrinkTo.startLine;
3738 toShrink.startColumn = rangeToShrinkTo.startColumn;
3739 }
3740 Q_ASSERT(toShrink.startLine == rangeToShrinkTo.startLine);
3741 if (toShrink.startColumn < rangeToShrinkTo.startColumn) {
3742 toShrink.startColumn = rangeToShrinkTo.startColumn;
3743 }
3744 }
3745 if (toShrink.endLine >= rangeToShrinkTo.endLine) {
3746 if (toShrink.endLine > rangeToShrinkTo.endLine) {
3747 toShrink.endLine = rangeToShrinkTo.endLine;
3748 toShrink.endColumn = rangeToShrinkTo.endColumn;
3749 }
3750 Q_ASSERT(toShrink.endLine == rangeToShrinkTo.endLine);
3751 if (toShrink.endColumn > rangeToShrinkTo.endColumn) {
3752 toShrink.endColumn = rangeToShrinkTo.endColumn;
3753 }
3754 }
3755}
3756
3757Range NormalViMode::textObjectComma(bool inner) const
3758{
3759 // Basic algorithm: look left and right of the cursor for all combinations
3760 // of enclosing commas and the various types of brackets, and pick the pair
3761 // closest to the cursor that surrounds the cursor.
3762 Range r(0, 0, m_view->doc()->lines(), m_view->doc()->line(m_view->doc()->lastLine()).length(), InclusiveMotion);
3763
3764 shrinkRangeAroundCursor(r, findSurroundingQuotes(QLatin1Char(','), inner));
3765 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('('), QLatin1Char(')'), inner, QLatin1Char('('), QLatin1Char(')')));
3766 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('{'), QLatin1Char('}'), inner, QLatin1Char('{'), QLatin1Char('}')));
3767 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char(','), QLatin1Char(')'), inner, QLatin1Char('('), QLatin1Char(')')));
3768 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char(','), QLatin1Char(']'), inner, QLatin1Char('['), QLatin1Char(']')));
3769 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char(','), QLatin1Char('}'), inner, QLatin1Char('{'), QLatin1Char('}')));
3770 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('('), QLatin1Char(','), inner, QLatin1Char('('), QLatin1Char(')')));
3771 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('['), QLatin1Char(','), inner, QLatin1Char('['), QLatin1Char(']')));
3772 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('{'), QLatin1Char(','), inner, QLatin1Char('{'), QLatin1Char('}')));
3773 return r;
3774}
3775
3776void NormalViMode::updateYankHighlightAttrib()
3777{
3778 if (!m_highlightYankAttribute) {
3779 m_highlightYankAttribute = new KTextEditor::Attribute;
3780 }
3781 const QColor &yankedColor = m_view->rendererConfig()->savedLineColor();
3782 m_highlightYankAttribute->setBackground(yankedColor);
3784 mouseInAttribute->setFontBold(true);
3785 m_highlightYankAttribute->setDynamicAttribute(KTextEditor::Attribute::ActivateMouseIn, mouseInAttribute);
3786 m_highlightYankAttribute->dynamicAttribute(KTextEditor::Attribute::ActivateMouseIn)->setBackground(yankedColor);
3787}
3788
3789void NormalViMode::highlightYank(const Range &range, const OperationMode mode)
3790{
3791 clearYankHighlight();
3792
3793 // current MovingRange doesn't support block mode selection so split the
3794 // block range into per-line ranges
3795 if (mode == Block) {
3796 for (int i = range.startLine; i <= range.endLine; i++) {
3797 addHighlightYank(KTextEditor::Range(i, range.startColumn, i, range.endColumn));
3798 }
3799 } else {
3800 addHighlightYank(KTextEditor::Range(range.startLine, range.startColumn, range.endLine, range.endColumn));
3801 }
3802}
3803
3804void NormalViMode::addHighlightYank(KTextEditor::Range yankRange)
3805{
3806 KTextEditor::MovingRange *highlightedYank = m_view->doc()->newMovingRange(yankRange, Kate::TextRange::DoNotExpand);
3807 highlightedYank->setView(m_view); // show only in this view
3808 highlightedYank->setAttributeOnlyForViews(true);
3809 // use z depth defined in moving ranges interface
3810 highlightedYank->setZDepth(-10000.0);
3811 highlightedYank->setAttribute(m_highlightYankAttribute);
3812
3813 highlightedYankForDocument().insert(highlightedYank);
3814}
3815
3816void NormalViMode::clearYankHighlight()
3817{
3818 QSet<KTextEditor::MovingRange *> &pHighlightedYanks = highlightedYankForDocument();
3819 qDeleteAll(pHighlightedYanks);
3820 pHighlightedYanks.clear();
3821}
3822
3823void NormalViMode::aboutToDeleteMovingInterfaceContent()
3824{
3825 QSet<KTextEditor::MovingRange *> &pHighlightedYanks = highlightedYankForDocument();
3826 // Prevent double-deletion in case this NormalMode is deleted.
3827 pHighlightedYanks.clear();
3828}
3829
3830QSet<KTextEditor::MovingRange *> &NormalViMode::highlightedYankForDocument()
3831{
3832 // Work around the fact that both Normal and Visual mode will have their own m_highlightedYank -
3833 // make Normal's the canonical one.
3834 return m_viInputModeManager->getViNormalMode()->m_highlightedYanks;
3835}
3836
3837bool NormalViMode::waitingForRegisterOrCharToSearch()
3838{
3839 // r, q, @ are never preceded by operators. There will always be a keys size of 1 for them.
3840 // f, t, F, T can be preceded by a delete/replace/yank/indent operator. size = 2 in that case.
3841 // f, t, F, T can be preceded by 'g' case/formatting operators. size = 3 in that case.
3842 const int keysSize = m_keys.size();
3843 if (keysSize < 1) {
3844 // Just being defensive there.
3845 return false;
3846 }
3847 if (keysSize > 1) {
3848 // Multi-letter operation.
3849 QChar cPrefix = m_keys[0];
3850 if (keysSize == 2) {
3851 // delete/replace/yank/indent operator?
3852 if (cPrefix != QLatin1Char('c') && cPrefix != QLatin1Char('d') && cPrefix != QLatin1Char('y') && cPrefix != QLatin1Char('=')
3853 && cPrefix != QLatin1Char('>') && cPrefix != QLatin1Char('<')) {
3854 return false;
3855 }
3856 } else if (keysSize == 3) {
3857 // We need to look deeper. Is it a g motion?
3858 QChar cNextfix = m_keys[1];
3859 if (cPrefix != QLatin1Char('g')
3860 || (cNextfix != QLatin1Char('U') && cNextfix != QLatin1Char('u') && cNextfix != QLatin1Char('~') && cNextfix != QLatin1Char('q')
3861 && cNextfix != QLatin1Char('w') && cNextfix != QLatin1Char('@'))) {
3862 return false;
3863 }
3864 } else {
3865 return false;
3866 }
3867 }
3868
3869 QChar ch = m_keys[keysSize - 1];
3870 return (ch == QLatin1Char('f') || ch == QLatin1Char('t') || ch == QLatin1Char('F')
3871 || ch == QLatin1Char('T')
3872 // c/d prefix unapplicable for the following cases.
3873 || (keysSize == 1 && (ch == QLatin1Char('r') || ch == QLatin1Char('q') || ch == QLatin1Char('@'))));
3874}
3875
3876void NormalViMode::textInserted(KTextEditor::Document *document, KTextEditor::Range range)
3877{
3878 if (m_viInputModeManager->view()->viewInputMode() != KTextEditor::View::ViInputMode) {
3879 return;
3880 }
3881
3882 Q_UNUSED(document)
3883 const bool isInsertReplaceMode =
3884 (m_viInputModeManager->getCurrentViMode() == ViMode::InsertMode || m_viInputModeManager->getCurrentViMode() == ViMode::ReplaceMode);
3885 const bool continuesInsertion = range.start().line() == m_currentChangeEndMarker.line() && range.start().column() == m_currentChangeEndMarker.column();
3886 const bool beginsWithNewline = doc()->text(range).at(0) == QLatin1Char('\n');
3887 if (!continuesInsertion) {
3888 KTextEditor::Cursor newBeginMarkerPos = range.start();
3889 if (beginsWithNewline && !isInsertReplaceMode) {
3890 // Presumably a linewise paste, in which case we ignore the leading '\n'
3891 newBeginMarkerPos = KTextEditor::Cursor(newBeginMarkerPos.line() + 1, 0);
3892 }
3893 m_viInputModeManager->marks()->setStartEditYanked(newBeginMarkerPos);
3894 }
3895 m_viInputModeManager->marks()->setLastChange(range.start());
3896 KTextEditor::Cursor editEndMarker = range.end();
3897 if (!isInsertReplaceMode) {
3898 editEndMarker.setColumn(editEndMarker.column() - 1);
3899 }
3900 m_viInputModeManager->marks()->setFinishEditYanked(editEndMarker);
3901 m_currentChangeEndMarker = range.end();
3902 if (m_isUndo) {
3903 const bool addsMultipleLines = range.start().line() != range.end().line();
3904 m_viInputModeManager->marks()->setStartEditYanked(KTextEditor::Cursor(m_viInputModeManager->marks()->getStartEditYanked().line(), 0));
3905 if (addsMultipleLines) {
3906 m_viInputModeManager->marks()->setFinishEditYanked(KTextEditor::Cursor(m_viInputModeManager->marks()->getFinishEditYanked().line() + 1, 0));
3907 m_viInputModeManager->marks()->setLastChange(KTextEditor::Cursor(m_viInputModeManager->marks()->getLastChange().line() + 1, 0));
3908 } else {
3909 m_viInputModeManager->marks()->setFinishEditYanked(KTextEditor::Cursor(m_viInputModeManager->marks()->getFinishEditYanked().line(), 0));
3910 m_viInputModeManager->marks()->setLastChange(KTextEditor::Cursor(m_viInputModeManager->marks()->getLastChange().line(), 0));
3911 }
3912 }
3913}
3914
3915void NormalViMode::textRemoved(KTextEditor::Document *document, KTextEditor::Range range)
3916{
3917 if (m_viInputModeManager->view()->viewInputMode() != KTextEditor::View::ViInputMode) {
3918 return;
3919 }
3920
3921 Q_UNUSED(document);
3922 const bool isInsertReplaceMode =
3923 (m_viInputModeManager->getCurrentViMode() == ViMode::InsertMode || m_viInputModeManager->getCurrentViMode() == ViMode::ReplaceMode);
3924 m_viInputModeManager->marks()->setLastChange(range.start());
3925 if (!isInsertReplaceMode) {
3926 // Don't go resetting [ just because we did a Ctrl-h!
3927 m_viInputModeManager->marks()->setStartEditYanked(range.start());
3928 } else {
3929 // Don't go disrupting our continued insertion just because we did a Ctrl-h!
3930 m_currentChangeEndMarker = range.start();
3931 }
3932 m_viInputModeManager->marks()->setFinishEditYanked(range.start());
3933 if (m_isUndo) {
3934 // Slavishly follow Vim's weird rules: if an undo removes several lines, then all markers should
3935 // be at the beginning of the line after the last line removed, else they should at the beginning
3936 // of the line above that.
3937 const int markerLineAdjustment = (range.start().line() != range.end().line()) ? 1 : 0;
3938 m_viInputModeManager->marks()->setStartEditYanked(
3939 KTextEditor::Cursor(m_viInputModeManager->marks()->getStartEditYanked().line() + markerLineAdjustment, 0));
3940 m_viInputModeManager->marks()->setFinishEditYanked(
3941 KTextEditor::Cursor(m_viInputModeManager->marks()->getFinishEditYanked().line() + markerLineAdjustment, 0));
3942 m_viInputModeManager->marks()->setLastChange(KTextEditor::Cursor(m_viInputModeManager->marks()->getLastChange().line() + markerLineAdjustment, 0));
3943 }
3944}
3945
3946void NormalViMode::undoBeginning()
3947{
3948 m_isUndo = true;
3949}
3950
3951void NormalViMode::undoEnded()
3952{
3953 m_isUndo = false;
3954}
3955
3956bool NormalViMode::executeKateCommand(const QString &command)
3957{
3958 KTextEditor::Command *p = KateCmd::self()->queryCommand(command);
3959
3960 if (!p) {
3961 return false;
3962 }
3963
3964 QString msg;
3965 return p->exec(m_view, command, msg);
3966}
3967
3968#define ADDCMD(STR, FUNC, FLGS) Command(QStringLiteral(STR), &NormalViMode::FUNC, FLGS)
3969
3970#define ADDMOTION(STR, FUNC, FLGS) Motion(QStringLiteral(STR), &NormalViMode::FUNC, FLGS)
3971
3972const std::vector<Command> &NormalViMode::commands()
3973{
3974 // init once, is expensive
3975 static const std::vector<Command> global{
3976 ADDCMD("a", commandEnterInsertModeAppend, IS_CHANGE),
3977 ADDCMD("A", commandEnterInsertModeAppendEOL, IS_CHANGE),
3978 ADDCMD("i", commandEnterInsertMode, IS_CHANGE),
3979 ADDCMD("<insert>", commandEnterInsertMode, IS_CHANGE),
3980 ADDCMD("I", commandEnterInsertModeBeforeFirstNonBlankInLine, IS_CHANGE),
3981 ADDCMD("gi", commandEnterInsertModeLast, IS_CHANGE),
3982 ADDCMD("v", commandEnterVisualMode, 0),
3983 ADDCMD("V", commandEnterVisualLineMode, 0),
3984 ADDCMD("<c-v>", commandEnterVisualBlockMode, 0),
3985 ADDCMD("gv", commandReselectVisual, SHOULD_NOT_RESET),
3986 ADDCMD("o", commandOpenNewLineUnder, IS_CHANGE),
3987 ADDCMD("O", commandOpenNewLineOver, IS_CHANGE),
3988 ADDCMD("J", commandJoinLines, IS_CHANGE),
3989 ADDCMD("c", commandChange, IS_CHANGE | NEEDS_MOTION),
3990 ADDCMD("C", commandChangeToEOL, IS_CHANGE),
3991 ADDCMD("cc", commandChangeLine, IS_CHANGE),
3992 ADDCMD("s", commandSubstituteChar, IS_CHANGE),
3993 ADDCMD("S", commandSubstituteLine, IS_CHANGE),
3994 ADDCMD("dd", commandDeleteLine, IS_CHANGE),
3995 ADDCMD("d", commandDelete, IS_CHANGE | NEEDS_MOTION),
3996 ADDCMD("D", commandDeleteToEOL, IS_CHANGE),
3997 ADDCMD("x", commandDeleteChar, IS_CHANGE),
3998 ADDCMD("<delete>", commandDeleteChar, IS_CHANGE),
3999 ADDCMD("X", commandDeleteCharBackward, IS_CHANGE),
4000 ADDCMD("gu", commandMakeLowercase, IS_CHANGE | NEEDS_MOTION),
4001 ADDCMD("guu", commandMakeLowercaseLine, IS_CHANGE),
4002 ADDCMD("gU", commandMakeUppercase, IS_CHANGE | NEEDS_MOTION),
4003 ADDCMD("gUU", commandMakeUppercaseLine, IS_CHANGE),
4004 ADDCMD("y", commandYank, NEEDS_MOTION),
4005 ADDCMD("yy", commandYankLine, 0),
4006 ADDCMD("Y", commandYankToEOL, 0),
4007 ADDCMD("p", commandPaste, IS_CHANGE),
4008 ADDCMD("P", commandPasteBefore, IS_CHANGE),
4009 ADDCMD("gp", commandgPaste, IS_CHANGE),
4010 ADDCMD("gP", commandgPasteBefore, IS_CHANGE),
4011 ADDCMD("]p", commandIndentedPaste, IS_CHANGE),
4012 ADDCMD("[p", commandIndentedPasteBefore, IS_CHANGE),
4013 ADDCMD("r.", commandReplaceCharacter, IS_CHANGE | REGEX_PATTERN),
4014 ADDCMD("R", commandEnterReplaceMode, IS_CHANGE),
4015 ADDCMD(":", commandSwitchToCmdLine, 0),
4016 ADDCMD("u", commandUndo, 0),
4017 ADDCMD("<c-r>", commandRedo, 0),
4018 ADDCMD("U", commandRedo, 0),
4019 ADDCMD("m.", commandSetMark, REGEX_PATTERN),
4020 ADDCMD(">>", commandIndentLine, IS_CHANGE),
4021 ADDCMD("<<", commandUnindentLine, IS_CHANGE),
4022 ADDCMD(">", commandIndentLines, IS_CHANGE | NEEDS_MOTION),
4023 ADDCMD("<", commandUnindentLines, IS_CHANGE | NEEDS_MOTION),
4024 ADDCMD("<c-f>", commandScrollPageDown, 0),
4025 ADDCMD("<pagedown>", commandScrollPageDown, 0),
4026 ADDCMD("<c-b>", commandScrollPageUp, 0),
4027 ADDCMD("<pageup>", commandScrollPageUp, 0),
4028 ADDCMD("<c-u>", commandScrollHalfPageUp, 0),
4029 ADDCMD("<c-d>", commandScrollHalfPageDown, 0),
4030 ADDCMD("z.", commandCenterViewOnNonBlank, 0),
4031 ADDCMD("zz", commandCenterViewOnCursor, 0),
4032 ADDCMD("z<return>", commandTopViewOnNonBlank, 0),
4033 ADDCMD("zt", commandTopViewOnCursor, 0),
4034 ADDCMD("z-", commandBottomViewOnNonBlank, 0),
4035 ADDCMD("zb", commandBottomViewOnCursor, 0),
4036 ADDCMD("ga", commandPrintCharacterCode, SHOULD_NOT_RESET),
4037 ADDCMD(".", commandRepeatLastChange, 0),
4038 ADDCMD("==", commandAlignLine, IS_CHANGE),
4039 ADDCMD("=", commandAlignLines, IS_CHANGE | NEEDS_MOTION),
4040 ADDCMD("~", commandChangeCase, IS_CHANGE),
4041 ADDCMD("g~", commandChangeCaseRange, IS_CHANGE | NEEDS_MOTION),
4042 ADDCMD("g~~", commandChangeCaseLine, IS_CHANGE),
4043 ADDCMD("<c-a>", commandAddToNumber, IS_CHANGE),
4044 ADDCMD("<c-x>", commandSubtractFromNumber, IS_CHANGE),
4045 ADDCMD("<c-o>", commandGoToPrevJump, CAN_LAND_INSIDE_FOLDING_RANGE),
4046 ADDCMD("<c-i>", commandGoToNextJump, CAN_LAND_INSIDE_FOLDING_RANGE),
4047
4048 ADDCMD("<c-w>h", commandSwitchToLeftView, 0),
4049 ADDCMD("<c-w><c-h>", commandSwitchToLeftView, 0),
4050 ADDCMD("<c-w><left>", commandSwitchToLeftView, 0),
4051 ADDCMD("<c-w>j", commandSwitchToDownView, 0),
4052 ADDCMD("<c-w><c-j>", commandSwitchToDownView, 0),
4053 ADDCMD("<c-w><down>", commandSwitchToDownView, 0),
4054 ADDCMD("<c-w>k", commandSwitchToUpView, 0),
4055 ADDCMD("<c-w><c-k>", commandSwitchToUpView, 0),
4056 ADDCMD("<c-w><up>", commandSwitchToUpView, 0),
4057 ADDCMD("<c-w>l", commandSwitchToRightView, 0),
4058 ADDCMD("<c-w><c-l>", commandSwitchToRightView, 0),
4059 ADDCMD("<c-w><right>", commandSwitchToRightView, 0),
4060 ADDCMD("<c-w>w", commandSwitchToNextView, 0),
4061 ADDCMD("<c-w><c-w>", commandSwitchToNextView, 0),
4062
4063 ADDCMD("<c-w>s", commandSplitHoriz, 0),
4064 ADDCMD("<c-w>S", commandSplitHoriz, 0),
4065 ADDCMD("<c-w><c-s>", commandSplitHoriz, 0),
4066 ADDCMD("<c-w>v", commandSplitVert, 0),
4067 ADDCMD("<c-w><c-v>", commandSplitVert, 0),
4068 ADDCMD("<c-w>c", commandCloseView, 0),
4069
4070 ADDCMD("gt", commandSwitchToNextTab, 0),
4071 ADDCMD("gT", commandSwitchToPrevTab, 0),
4072
4073 ADDCMD("gqq", commandFormatLine, IS_CHANGE),
4074 ADDCMD("gq", commandFormatLines, IS_CHANGE | NEEDS_MOTION),
4075
4076 ADDCMD("zo", commandExpandLocal, 0),
4077 ADDCMD("zc", commandCollapseLocal, 0),
4078 ADDCMD("za", commandToggleRegionVisibility, 0),
4079 ADDCMD("zr", commandExpandAll, 0),
4080 ADDCMD("zm", commandCollapseToplevelNodes, 0),
4081
4082 ADDCMD("q.", commandStartRecordingMacro, REGEX_PATTERN),
4083 ADDCMD("@.", commandReplayMacro, REGEX_PATTERN),
4084
4085 ADDCMD("ZZ", commandCloseWrite, 0),
4086 ADDCMD("ZQ", commandCloseNocheck, 0),
4087 };
4088 return global;
4089}
4090
4091const std::vector<Motion> &NormalViMode::motions()
4092{
4093 // init once, is expensive
4094 static const std::vector<Motion> global{
4095 // regular motions
4096 ADDMOTION("h", motionLeft, 0),
4097 ADDMOTION("<left>", motionLeft, 0),
4098 ADDMOTION("<backspace>", motionLeft, 0),
4099 ADDMOTION("j", motionDown, 0),
4100 ADDMOTION("<down>", motionDown, 0),
4101 ADDMOTION("<enter>", motionDownToFirstNonBlank, 0),
4102 ADDMOTION("<return>", motionDownToFirstNonBlank, 0),
4103 ADDMOTION("k", motionUp, 0),
4104 ADDMOTION("<up>", motionUp, 0),
4105 ADDMOTION("-", motionUpToFirstNonBlank, 0),
4106 ADDMOTION("+", motionDownToFirstNonBlank, 0),
4107 ADDMOTION("l", motionRight, 0),
4108 ADDMOTION("<right>", motionRight, 0),
4109 ADDMOTION(" ", motionRight, 0),
4110 ADDMOTION("$", motionToEOL, 0),
4111 ADDMOTION("g_", motionToLastNonBlank, 0),
4112 ADDMOTION("<end>", motionToEOL, 0),
4113 ADDMOTION("0", motionToColumn0, 0),
4114 ADDMOTION("<home>", motionToColumn0, 0),
4115 ADDMOTION("^", motionToFirstCharacterOfLine, 0),
4116 ADDMOTION("f.", motionFindChar, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
4117 ADDMOTION("F.", motionFindCharBackward, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
4118 ADDMOTION("t.", motionToChar, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
4119 ADDMOTION("T.", motionToCharBackward, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
4120 ADDMOTION(";", motionRepeatlastTF, CAN_LAND_INSIDE_FOLDING_RANGE),
4121 ADDMOTION(",", motionRepeatlastTFBackward, CAN_LAND_INSIDE_FOLDING_RANGE),
4122 ADDMOTION("n", motionFindNext, CAN_LAND_INSIDE_FOLDING_RANGE),
4123 ADDMOTION("N", motionFindPrev, CAN_LAND_INSIDE_FOLDING_RANGE),
4124 ADDMOTION("gg", motionToLineFirst, 0),
4125 ADDMOTION("G", motionToLineLast, 0),
4126 ADDMOTION("w", motionWordForward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4127 ADDMOTION("W", motionWORDForward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4128 ADDMOTION("<c-right>", motionWordForward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4129 ADDMOTION("<c-left>", motionWordBackward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4130 ADDMOTION("b", motionWordBackward, CAN_LAND_INSIDE_FOLDING_RANGE),
4131 ADDMOTION("B", motionWORDBackward, CAN_LAND_INSIDE_FOLDING_RANGE),
4132 ADDMOTION("e", motionToEndOfWord, CAN_LAND_INSIDE_FOLDING_RANGE),
4133 ADDMOTION("E", motionToEndOfWORD, CAN_LAND_INSIDE_FOLDING_RANGE),
4134 ADDMOTION("ge", motionToEndOfPrevWord, CAN_LAND_INSIDE_FOLDING_RANGE),
4135 ADDMOTION("gE", motionToEndOfPrevWORD, CAN_LAND_INSIDE_FOLDING_RANGE),
4136 ADDMOTION("|", motionToScreenColumn, 0),
4137 ADDMOTION("%", motionToMatchingItem, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4138 ADDMOTION("`[a-zA-Z^><\\.\\[\\]]", motionToMark, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
4139 ADDMOTION("'[a-zA-Z^><]", motionToMarkLine, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE),
4140 ADDMOTION("[[", motionToPreviousBraceBlockStart, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4141 ADDMOTION("]]", motionToNextBraceBlockStart, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4142 ADDMOTION("[]", motionToPreviousBraceBlockEnd, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4143 ADDMOTION("][", motionToNextBraceBlockEnd, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4144 ADDMOTION("*", motionToNextOccurrence, CAN_LAND_INSIDE_FOLDING_RANGE),
4145 ADDMOTION("#", motionToPrevOccurrence, CAN_LAND_INSIDE_FOLDING_RANGE),
4146 ADDMOTION("H", motionToFirstLineOfWindow, 0),
4147 ADDMOTION("M", motionToMiddleLineOfWindow, 0),
4148 ADDMOTION("L", motionToLastLineOfWindow, 0),
4149 ADDMOTION("gj", motionToNextVisualLine, 0),
4150 ADDMOTION("g<down>", motionToNextVisualLine, 0),
4151 ADDMOTION("gk", motionToPrevVisualLine, 0),
4152 ADDMOTION("g<up>", motionToPrevVisualLine, 0),
4153 ADDMOTION("(", motionToPreviousSentence, CAN_LAND_INSIDE_FOLDING_RANGE),
4154 ADDMOTION(")", motionToNextSentence, CAN_LAND_INSIDE_FOLDING_RANGE),
4155 ADDMOTION("{", motionToBeforeParagraph, CAN_LAND_INSIDE_FOLDING_RANGE),
4156 ADDMOTION("}", motionToAfterParagraph, CAN_LAND_INSIDE_FOLDING_RANGE),
4157
4158 // text objects
4159 ADDMOTION("iw", textObjectInnerWord, 0),
4160 ADDMOTION("aw", textObjectAWord, IS_NOT_LINEWISE),
4161 ADDMOTION("iW", textObjectInnerWORD, 0),
4162 ADDMOTION("aW", textObjectAWORD, IS_NOT_LINEWISE),
4163 ADDMOTION("is", textObjectInnerSentence, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4164 ADDMOTION("as", textObjectASentence, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4165 ADDMOTION("ip", textObjectInnerParagraph, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4166 ADDMOTION("ap", textObjectAParagraph, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4167 ADDMOTION("i\"", textObjectInnerQuoteDouble, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4168 ADDMOTION("a\"", textObjectAQuoteDouble, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4169 ADDMOTION("i'", textObjectInnerQuoteSingle, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4170 ADDMOTION("a'", textObjectAQuoteSingle, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4171 ADDMOTION("i`", textObjectInnerBackQuote, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4172 ADDMOTION("a`", textObjectABackQuote, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4173 ADDMOTION("i[()b]", textObjectInnerParen, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4174 ADDMOTION("a[()b]", textObjectAParen, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4175 ADDMOTION("i[{}B]", textObjectInnerCurlyBracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4176 ADDMOTION("a[{}B]", textObjectACurlyBracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4177 ADDMOTION("i[><]", textObjectInnerInequalitySign, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4178 ADDMOTION("a[><]", textObjectAInequalitySign, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4179 ADDMOTION("i[\\[\\]]", textObjectInnerBracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4180 ADDMOTION("a[\\[\\]]", textObjectABracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4181 ADDMOTION("i,", textObjectInnerComma, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4182 ADDMOTION("a,", textObjectAComma, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4183
4184 ADDMOTION("/<enter>", motionToIncrementalSearchMatch, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4185 ADDMOTION("?<enter>", motionToIncrementalSearchMatch, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE),
4186 };
4187 return global;
4188}
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.
constexpr bool isValid() const noexcept
Validity check.
void configChanged(KTextEditor::View *view)
This signal is emitted whenever the current view configuration is changed.
@ ViInputMode
Vi mode.
Definition view.h:288
bool handleKeypress(const QKeyEvent *e) override
parses a key stroke to check if it's a valid (part of) a command
virtual const std::vector< Command > & commands()
Return commands available for this mode.
bool commandEnterInsertModeLast()
enter insert mode at the last insert position
void resetParser()
(re)set to start configuration.
bool commandEnterInsertModeAppendEOL()
start insert mode after the last character of the line
bool commandEnterInsertMode()
enter insert mode at the cursor position
int getFirstNonBlank(int line=-1) const
Get the index of the first non-blank character from the given line.
virtual const std::vector< Motion > & motions()
Return motions available for this mode.
bool commandEnterInsertModeAppend()
enter insert mode after the current character
int lineToVisibleLine(int line) const
Convert a text buffer line to a visible line number.
int visibleLineToLine(int visibleLine) const
Convert a visible line number to a line number in the text buffer.
Class representing a single text line.
int firstChar() const
Returns the position of the first non-whitespace character.
Q_SCRIPTABLE Q_NOREPLY void start()
QString i18n(const char *text, const TYPE &arg...)
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
const QList< QKeySequence > & end()
bool isLower(char32_t ucs4)
bool isSpace(char32_t ucs4)
bool isUpper(char32_t ucs4)
char32_t toLower(char32_t ucs4)
char32_t toUpper(char32_t ucs4)
char16_t & unicode()
ShortcutOverride
Type type() const const
int key() const const
Qt::KeyboardModifiers modifiers() const const
const_reference at(qsizetype i) const const
void clear()
bool isEmpty() const const
void push_back(parameter_type value)
void remove(qsizetype i, qsizetype n)
qsizetype size() const const
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
void clear()
iterator insert(const T &value)
void push(const T &t)
T & top()
qsizetype count() const const
QString & append(QChar ch)
const QChar at(qsizetype position) const const
void chop(qsizetype n)
void clear()
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
qsizetype length() const const
QString mid(qsizetype position, qsizetype n) const const
QString number(double n, char format, int precision)
QString & prepend(QChar ch)
QString & remove(QChar ch, Qt::CaseSensitivity cs)
QString repeated(qsizetype times) const const
QString & replace(QChar before, QChar after, Qt::CaseSensitivity cs)
QString right(qsizetype n) const const
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
QString toLower() const const
QString toUpper() const const
QString trimmed() const const
QStringView right(qsizetype length) const const
Key_Shift
NoModifier
QTextStream & dec(QTextStream &stream)
QTextStream & hex(QTextStream &stream)
QTextStream & oct(QTextStream &stream)
QFuture< QtPrivate::MapResultType< Iterator, MapFunctor > > mapped(Iterator begin, Iterator end, MapFunctor &&function)
QFuture< ArgsType< Signal > > connect(Sender *sender, Signal signal)
void repaint()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:15:44 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.