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