File indexing completed on 2024-05-12 07:53:47
0001 /* 0002 SPDX-FileCopyrightText: 2008-2009 Erlend Hamberg <ehamberg@gmail.com> 0003 SPDX-FileCopyrightText: 2008 Evgeniy Ivanov <powerfox@kde.ru> 0004 SPDX-FileCopyrightText: 2009 Paul Gideon Dann <pdgiddie@gmail.com> 0005 SPDX-FileCopyrightText: 2011 Svyatoslav Kuzmich <svatoslav1@gmail.com> 0006 SPDX-FileCopyrightText: 2012-2013 Simon St James <kdedevel@etotheipiplusone.com> 0007 0008 SPDX-License-Identifier: LGPL-2.0-or-later 0009 */ 0010 0011 #include "katebuffer.h" 0012 #include "katecmd.h" 0013 #include "katecompletionwidget.h" 0014 #include "kateconfig.h" 0015 #include "katedocument.h" 0016 #include "kateglobal.h" 0017 #include "katepartdebug.h" 0018 #include "katerenderer.h" 0019 #include "kateundomanager.h" 0020 #include "kateviewhelpers.h" 0021 #include "kateviewinternal.h" 0022 #include "kateviinputmode.h" 0023 #include <ktexteditor/attribute.h> 0024 #include <vimode/emulatedcommandbar/emulatedcommandbar.h> 0025 #include <vimode/globalstate.h> 0026 #include <vimode/history.h> 0027 #include <vimode/inputmodemanager.h> 0028 #include <vimode/keymapper.h> 0029 #include <vimode/keyparser.h> 0030 #include <vimode/lastchangerecorder.h> 0031 #include <vimode/macrorecorder.h> 0032 #include <vimode/marks.h> 0033 #include <vimode/modes/insertvimode.h> 0034 #include <vimode/modes/normalvimode.h> 0035 #include <vimode/modes/replacevimode.h> 0036 #include <vimode/modes/visualvimode.h> 0037 #include <vimode/registers.h> 0038 #include <vimode/searcher.h> 0039 0040 #include <KLocalizedString> 0041 #include <QApplication> 0042 #include <QList> 0043 0044 using namespace KateVi; 0045 0046 NormalViMode::NormalViMode(InputModeManager *viInputModeManager, KTextEditor::ViewPrivate *view, KateViewInternal *viewInternal) 0047 : ModeBase() 0048 { 0049 m_view = view; 0050 m_viewInternal = viewInternal; 0051 m_viInputModeManager = viInputModeManager; 0052 m_stickyColumn = -1; 0053 m_lastMotionWasVisualLineUpOrDown = false; 0054 m_currentMotionWasVisualLineUpOrDown = false; 0055 0056 // FIXME: make configurable 0057 m_extraWordCharacters = QString(); 0058 m_matchingItems[QStringLiteral("/*")] = QStringLiteral("*/"); 0059 m_matchingItems[QStringLiteral("*/")] = QStringLiteral("-/*"); 0060 0061 m_matchItemRegex = generateMatchingItemRegex(); 0062 0063 m_scroll_count_limit = 1000; // Limit of count for scroll commands. 0064 0065 m_pendingResetIsDueToExit = false; 0066 m_isRepeatedTFcommand = false; 0067 m_lastMotionWasLinewiseInnerBlock = false; 0068 m_motionCanChangeWholeVisualModeSelection = false; 0069 resetParser(); // initialise with start configuration 0070 0071 m_isUndo = false; 0072 connect(doc()->undoManager(), &KateUndoManager::undoStart, this, &NormalViMode::undoBeginning); 0073 connect(doc()->undoManager(), &KateUndoManager::undoEnd, this, &NormalViMode::undoEnded); 0074 0075 updateYankHighlightAttrib(); 0076 connect(view, &KTextEditor::View::configChanged, this, &NormalViMode::updateYankHighlightAttrib); 0077 connect(doc(), &KTextEditor::DocumentPrivate::aboutToInvalidateMovingInterfaceContent, this, &NormalViMode::clearYankHighlight); 0078 connect(doc(), &KTextEditor::DocumentPrivate::aboutToDeleteMovingInterfaceContent, this, &NormalViMode::aboutToDeleteMovingInterfaceContent); 0079 } 0080 0081 NormalViMode::~NormalViMode() 0082 { 0083 qDeleteAll(m_highlightedYanks); 0084 } 0085 0086 /** 0087 * parses a key stroke to check if it's a valid (part of) a command 0088 * @return true if a command was completed and executed, false otherwise 0089 */ 0090 bool NormalViMode::handleKeypress(const QKeyEvent *e) 0091 { 0092 const int keyCode = e->key(); 0093 0094 // ignore modifier keys alone 0095 if (keyCode == Qt::Key_Shift || keyCode == Qt::Key_Control || keyCode == Qt::Key_Alt || keyCode == Qt::Key_Meta) { 0096 return false; 0097 } 0098 0099 clearYankHighlight(); 0100 0101 if (keyCode == Qt::Key_Escape || (keyCode == Qt::Key_C && e->modifiers() == CONTROL_MODIFIER) 0102 || (keyCode == Qt::Key_BracketLeft && e->modifiers() == CONTROL_MODIFIER)) { 0103 m_viInputModeManager->inputAdapter()->setCaretStyle(KTextEditor::caretStyles::Block); 0104 m_pendingResetIsDueToExit = true; 0105 // 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, 0106 // but behaves as if it's in normal mode. I'm treating the status bar thing as a bug and just exiting 0107 // insert mode altogether. 0108 m_viInputModeManager->setTemporaryNormalMode(false); 0109 reset(); 0110 return true; 0111 } 0112 0113 const QChar key = KeyParser::self()->KeyEventToQChar(*e); 0114 0115 const QChar lastChar = m_keys.isEmpty() ? QChar::Null : m_keys.at(m_keys.size() - 1); 0116 const bool waitingForRegisterOrCharToSearch = this->waitingForRegisterOrCharToSearch(); 0117 0118 // Use replace caret when reading a character for "r" 0119 if (key == QLatin1Char('r') && !waitingForRegisterOrCharToSearch) { 0120 m_viInputModeManager->inputAdapter()->setCaretStyle(KTextEditor::caretStyles::Underline); 0121 } 0122 0123 m_keysVerbatim.append(KeyParser::self()->decodeKeySequence(key)); 0124 0125 if ((keyCode >= Qt::Key_0 && keyCode <= Qt::Key_9 && lastChar != QLatin1Char('"')) // key 0-9 0126 && (m_countTemp != 0 || keyCode != Qt::Key_0) // first digit can't be 0 0127 && (!waitingForRegisterOrCharToSearch) // Not in the middle of "find char" motions or replacing char. 0128 && e->modifiers() == Qt::NoModifier) { 0129 m_countTemp *= 10; 0130 m_countTemp += keyCode - Qt::Key_0; 0131 0132 return true; 0133 } else if (m_countTemp != 0) { 0134 m_count = getCount() * m_countTemp; 0135 m_countTemp = 0; 0136 m_iscounted = true; 0137 } 0138 0139 m_keys.append(key); 0140 0141 if (m_viInputModeManager->macroRecorder()->isRecording() && key == QLatin1Char('q')) { 0142 // Need to special case this "finish macro" q, as the "begin macro" q 0143 // needs a parameter whereas the finish macro does not. 0144 m_viInputModeManager->macroRecorder()->stop(); 0145 resetParser(); 0146 return true; 0147 } 0148 0149 if ((key == QLatin1Char('/') || key == QLatin1Char('?')) && !waitingForRegisterOrCharToSearch) { 0150 // Special case for "/" and "?": these should be motions, but this is complicated by 0151 // the fact that the user must interact with the search bar before the range of the 0152 // motion can be determined. 0153 // We hack around this by showing the search bar immediately, and, when the user has 0154 // finished interacting with it, have the search bar send a "synthetic" keypresses 0155 // that will either abort everything (if the search was aborted) or "complete" the motion 0156 // otherwise. 0157 m_positionWhenIncrementalSearchBegan = m_view->cursorPosition(); 0158 if (key == QLatin1Char('/')) { 0159 commandSearchForward(); 0160 } else { 0161 commandSearchBackward(); 0162 } 0163 return true; 0164 } 0165 0166 // Special case: "cw" and "cW" work the same as "ce" and "cE" if the cursor is 0167 // on a non-blank. This is because Vim interprets "cw" as change-word, and a 0168 // word does not include the following white space. (:help cw in vim) 0169 if ((m_keys == QLatin1String("cw") || m_keys == QLatin1String("cW")) && !getCharUnderCursor().isSpace()) { 0170 // Special case of the special case: :-) 0171 // If the cursor is at the end of the current word rewrite to "cl" 0172 const bool isWORD = (m_keys.at(1) == QLatin1Char('W')); 0173 const KTextEditor::Cursor currentPosition(m_view->cursorPosition()); 0174 const KTextEditor::Cursor endOfWordOrWORD = (isWORD ? findWORDEnd(currentPosition.line(), currentPosition.column() - 1, true) 0175 : findWordEnd(currentPosition.line(), currentPosition.column() - 1, true)); 0176 0177 if (currentPosition == endOfWordOrWORD) { 0178 m_keys = QStringLiteral("cl"); 0179 } else { 0180 if (isWORD) { 0181 m_keys = QStringLiteral("cE"); 0182 } else { 0183 m_keys = QStringLiteral("ce"); 0184 } 0185 } 0186 } 0187 0188 if (m_keys[0] == QChar(Qt::Key_QuoteDbl)) { 0189 if (m_keys.size() < 2) { 0190 return true; // waiting for a register 0191 } else { 0192 QChar r = m_keys[1].toLower(); 0193 0194 if ((r >= QLatin1Char('0') && r <= QLatin1Char('9')) || (r >= QLatin1Char('a') && r <= QLatin1Char('z')) || r == QLatin1Char('_') 0195 || r == QLatin1Char('-') || r == QLatin1Char('+') || r == QLatin1Char('*') || r == QLatin1Char('#') || r == QLatin1Char('^')) { 0196 m_register = m_keys[1]; 0197 m_keys.clear(); 0198 return true; 0199 } else { 0200 resetParser(); 0201 return true; 0202 } 0203 } 0204 } 0205 0206 // if we have any matching commands so far, check which ones still match 0207 if (!m_matchingCommands.isEmpty()) { 0208 int n = m_matchingCommands.size() - 1; 0209 0210 // remove commands not matching anymore 0211 for (int i = n; i >= 0; i--) { 0212 if (!commands().at(m_matchingCommands.at(i)).matches(m_keys)) { 0213 if (commands().at(m_matchingCommands.at(i)).needsMotion()) { 0214 // "cache" command needing a motion for later 0215 m_motionOperatorIndex = m_matchingCommands.at(i); 0216 } 0217 m_matchingCommands.remove(i); 0218 } 0219 } 0220 0221 // check if any of the matching commands need a motion/text object, if so 0222 // push the current command length to m_awaitingMotionOrTextObject so one 0223 // knows where to split the command between the operator and the motion 0224 for (int i = 0; i < m_matchingCommands.size(); i++) { 0225 if (commands().at(m_matchingCommands.at(i)).needsMotion()) { 0226 m_awaitingMotionOrTextObject.push(m_keys.size()); 0227 break; 0228 } 0229 } 0230 } else { 0231 // go through all registered commands and put possible matches in m_matchingCommands 0232 for (size_t i = 0; i < commands().size(); i++) { 0233 if (commands().at(i).matches(m_keys)) { 0234 m_matchingCommands.push_back(i); 0235 if (commands().at(i).needsMotion() && commands().at(i).pattern().length() == m_keys.size()) { 0236 m_awaitingMotionOrTextObject.push(m_keys.size()); 0237 } 0238 } 0239 } 0240 } 0241 0242 // this indicates where in the command string one should start looking for a motion command 0243 int checkFrom = (m_awaitingMotionOrTextObject.isEmpty() ? 0 : m_awaitingMotionOrTextObject.top()); 0244 0245 // Use operator-pending caret when reading a motion for an operator 0246 // in normal mode. We need to check that we are indeed in normal mode 0247 // since visual mode inherits from it. 0248 if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode && !m_awaitingMotionOrTextObject.isEmpty()) { 0249 m_viInputModeManager->inputAdapter()->setCaretStyle(KTextEditor::caretStyles::Half); 0250 } 0251 0252 // look for matching motion commands from position 'checkFrom' 0253 // FIXME: if checkFrom hasn't changed, only motions whose index is in 0254 // m_matchingMotions should be checked 0255 bool motionExecuted = false; 0256 if (checkFrom < m_keys.size()) { 0257 for (size_t i = 0; i < motions().size(); i++) { 0258 const QString motion = m_keys.mid(checkFrom); 0259 if (motions().at(i).matches(motion)) { 0260 m_lastMotionWasLinewiseInnerBlock = false; 0261 m_matchingMotions.push_back(i); 0262 0263 // if it matches exact, we have found the motion command to execute 0264 if (motions().at(i).matchesExact(motion)) { 0265 m_currentMotionWasVisualLineUpOrDown = false; 0266 motionExecuted = true; 0267 if (checkFrom == 0) { 0268 // no command given before motion, just move the cursor to wherever 0269 // the motion says it should go to 0270 Range r = motions().at(i).execute(this); 0271 m_motionCanChangeWholeVisualModeSelection = motions().at(i).canChangeWholeVisualModeSelection(); 0272 0273 if (!motions().at(i).canLandInsideFoldingRange()) { 0274 // jump over folding regions since we are just moving the cursor 0275 // except for motions that can end up inside ranges (e.g. n/N, f/F, %, #) 0276 int currLine = m_view->cursorPosition().line(); 0277 int delta = r.endLine - currLine; 0278 int vline = m_view->textFolding().lineToVisibleLine(currLine); 0279 r.endLine = m_view->textFolding().visibleLineToLine(qMax(vline + delta, 0) /* ensure we have a valid line */); 0280 } 0281 0282 if (r.endLine >= doc()->lines()) { 0283 r.endLine = doc()->lines() - 1; 0284 } 0285 0286 // make sure the position is valid before moving the cursor there 0287 // TODO: can this be simplified? :/ 0288 if (r.valid && r.endLine >= 0 && (r.endLine == 0 || r.endLine <= doc()->lines() - 1) && r.endColumn >= 0) { 0289 if (r.endColumn >= doc()->lineLength(r.endLine) && doc()->lineLength(r.endLine) > 0) { 0290 r.endColumn = doc()->lineLength(r.endLine) - 1; 0291 } 0292 0293 goToPos(r); 0294 0295 // in the case of VisualMode we need to remember the motion commands as well. 0296 if (!m_viInputModeManager->isAnyVisualMode()) { 0297 m_viInputModeManager->clearCurrentChangeLog(); 0298 } 0299 } else { 0300 qCDebug(LOG_KTE) << "Invalid position: (" << r.endLine << "," << r.endColumn << ")"; 0301 } 0302 0303 resetParser(); 0304 0305 // if normal mode was started by using Ctrl-O in insert mode, 0306 // it's time to go back to insert mode. 0307 if (m_viInputModeManager->getTemporaryNormalMode()) { 0308 startInsertMode(); 0309 m_viewInternal->repaint(); 0310 } 0311 0312 m_lastMotionWasVisualLineUpOrDown = m_currentMotionWasVisualLineUpOrDown; 0313 0314 break; 0315 } else { 0316 // execute the specified command and supply the position returned from 0317 // the motion 0318 0319 m_commandRange = motions().at(i).execute(this); 0320 m_linewiseCommand = motions().at(i).isLineWise(); 0321 0322 // if we didn't get an explicit start position, use the current cursor position 0323 if (m_commandRange.startLine == -1) { 0324 KTextEditor::Cursor c(m_view->cursorPosition()); 0325 m_commandRange.startLine = c.line(); 0326 m_commandRange.startColumn = c.column(); 0327 } 0328 0329 // special case: When using the "w" motion in combination with an operator and 0330 // the last word moved over is at the end of a line, the end of that word 0331 // becomes the end of the operated text, not the first word in the next line. 0332 if (motions().at(i).pattern() == QLatin1String("w") || motions().at(i).pattern() == QLatin1String("W")) { 0333 if (m_commandRange.endLine != m_commandRange.startLine && m_commandRange.endColumn == getFirstNonBlank(m_commandRange.endLine)) { 0334 m_commandRange.endLine--; 0335 m_commandRange.endColumn = doc()->lineLength(m_commandRange.endLine); 0336 } 0337 } 0338 0339 m_commandWithMotion = true; 0340 0341 if (m_commandRange.valid) { 0342 executeCommand(&commands().at(m_motionOperatorIndex)); 0343 } else { 0344 qCDebug(LOG_KTE) << "Invalid range: " 0345 << "from (" << m_commandRange.startLine << "," << m_commandRange.startColumn << ")" 0346 << "to (" << m_commandRange.endLine << "," << m_commandRange.endColumn << ")"; 0347 } 0348 0349 if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) { 0350 m_viInputModeManager->inputAdapter()->setCaretStyle(KTextEditor::caretStyles::Block); 0351 } 0352 m_commandWithMotion = false; 0353 reset(); 0354 break; 0355 } 0356 } 0357 } 0358 } 0359 } 0360 0361 if (this->waitingForRegisterOrCharToSearch()) { 0362 // If we are waiting for a char to search or a new register, 0363 // don't translate next character; we need the actual character so that e.g. 0364 // 'ab' is translated to 'fb' if the mappings 'a' -> 'f' and 'b' -> something else 0365 // exist. 0366 m_viInputModeManager->keyMapper()->setDoNotMapNextKeypress(); 0367 } 0368 0369 if (motionExecuted) { 0370 return true; 0371 } 0372 0373 // if we have only one match, check if it is a perfect match and if so, execute it 0374 // if it's not waiting for a motion or a text object 0375 if (m_matchingCommands.size() == 1) { 0376 if (commands().at(m_matchingCommands.at(0)).matchesExact(m_keys) && !commands().at(m_matchingCommands.at(0)).needsMotion()) { 0377 if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) { 0378 m_viInputModeManager->inputAdapter()->setCaretStyle(KTextEditor::caretStyles::Block); 0379 } 0380 0381 const Command &cmd = commands().at(m_matchingCommands.at(0)); 0382 executeCommand(&cmd); 0383 0384 // check if reset() should be called. some commands in visual mode should not end visual mode 0385 if (cmd.shouldReset()) { 0386 reset(); 0387 m_view->setBlockSelection(false); 0388 } 0389 resetParser(); 0390 0391 return true; 0392 } 0393 } else if (m_matchingCommands.size() == 0 && m_matchingMotions.size() == 0) { 0394 resetParser(); 0395 // A bit ugly: we haven't made use of the key event, 0396 // but don't want "typeable" keypresses (e.g. a, b, 3, etc) to be marked 0397 // as unused as they will then be added to the document, but we don't 0398 // want to swallow all keys in case this was a shortcut. 0399 // So say we made use of it if and only if it was *not* a shortcut. 0400 return e->type() != QEvent::ShortcutOverride; 0401 } 0402 0403 m_matchingMotions.clear(); 0404 return true; // TODO - need to check this - it's currently required for making tests pass, but seems odd. 0405 } 0406 0407 /** 0408 * (re)set to start configuration. This is done when a command is completed 0409 * executed or when a command is aborted 0410 */ 0411 void NormalViMode::resetParser() 0412 { 0413 m_keys.clear(); 0414 m_keysVerbatim.clear(); 0415 m_count = 0; 0416 m_oneTimeCountOverride = -1; 0417 m_iscounted = false; 0418 m_countTemp = 0; 0419 m_register = QChar::Null; 0420 m_findWaitingForChar = false; 0421 m_matchingCommands.clear(); 0422 m_matchingMotions.clear(); 0423 m_awaitingMotionOrTextObject.clear(); 0424 m_motionOperatorIndex = 0; 0425 0426 m_commandWithMotion = false; 0427 m_linewiseCommand = true; 0428 m_deleteCommand = false; 0429 0430 m_commandShouldKeepSelection = false; 0431 0432 m_currentChangeEndMarker = KTextEditor::Cursor::invalid(); 0433 0434 if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) { 0435 m_viInputModeManager->inputAdapter()->setCaretStyle(KTextEditor::caretStyles::Block); 0436 } 0437 } 0438 0439 // reset the command parser 0440 void NormalViMode::reset() 0441 { 0442 resetParser(); 0443 m_commandRange.startLine = -1; 0444 m_commandRange.startColumn = -1; 0445 } 0446 0447 void NormalViMode::beginMonitoringDocumentChanges() 0448 { 0449 connect(doc(), &KTextEditor::DocumentPrivate::textInsertedRange, this, &NormalViMode::textInserted); 0450 connect(doc(), &KTextEditor::DocumentPrivate::textRemoved, this, &NormalViMode::textRemoved); 0451 } 0452 0453 void NormalViMode::executeCommand(const Command *cmd) 0454 { 0455 const ViMode originalViMode = m_viInputModeManager->getCurrentViMode(); 0456 0457 cmd->execute(this); 0458 0459 // if normal mode was started by using Ctrl-O in insert mode, 0460 // it's time to go back to insert mode. 0461 if (m_viInputModeManager->getTemporaryNormalMode()) { 0462 startInsertMode(); 0463 m_viewInternal->repaint(); 0464 } 0465 0466 // if the command was a change, and it didn't enter insert mode, store the key presses so that 0467 // they can be repeated with '.' 0468 if (m_viInputModeManager->getCurrentViMode() != ViMode::InsertMode && m_viInputModeManager->getCurrentViMode() != ViMode::ReplaceMode) { 0469 if (cmd->isChange() && !m_viInputModeManager->lastChangeRecorder()->isReplaying()) { 0470 m_viInputModeManager->storeLastChangeCommand(); 0471 } 0472 0473 // when we transition to visual mode, remember the command in the keys history (V, v, ctrl-v, ...) 0474 // this will later result in buffer filled with something like this "Vjj>" which we can use later with repeat "." 0475 const bool commandSwitchedToVisualMode = ((originalViMode == ViMode::NormalMode) && m_viInputModeManager->isAnyVisualMode()); 0476 if (!commandSwitchedToVisualMode) { 0477 m_viInputModeManager->clearCurrentChangeLog(); 0478 } 0479 } 0480 0481 // make sure the cursor does not end up after the end of the line 0482 KTextEditor::Cursor c(m_view->cursorPosition()); 0483 if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) { 0484 int lineLength = doc()->lineLength(c.line()); 0485 0486 if (c.column() >= lineLength) { 0487 if (lineLength == 0) { 0488 c.setColumn(0); 0489 } else { 0490 c.setColumn(lineLength - 1); 0491 } 0492 } 0493 updateCursor(c); 0494 } 0495 } 0496 0497 //////////////////////////////////////////////////////////////////////////////// 0498 // COMMANDS AND OPERATORS 0499 //////////////////////////////////////////////////////////////////////////////// 0500 0501 /** 0502 * enter insert mode at the cursor position 0503 */ 0504 0505 bool NormalViMode::commandEnterInsertMode() 0506 { 0507 m_stickyColumn = -1; 0508 m_viInputModeManager->getViInsertMode()->setCount(getCount()); 0509 return startInsertMode(); 0510 } 0511 0512 /** 0513 * enter insert mode after the current character 0514 */ 0515 0516 bool NormalViMode::commandEnterInsertModeAppend() 0517 { 0518 KTextEditor::Cursor c(m_view->cursorPosition()); 0519 c.setColumn(c.column() + 1); 0520 0521 // if empty line, the cursor should start at column 0 0522 if (doc()->lineLength(c.line()) == 0) { 0523 c.setColumn(0); 0524 } 0525 0526 // cursor should never be in a column > number of columns 0527 if (c.column() > doc()->lineLength(c.line())) { 0528 c.setColumn(doc()->lineLength(c.line())); 0529 } 0530 0531 updateCursor(c); 0532 0533 m_stickyColumn = -1; 0534 m_viInputModeManager->getViInsertMode()->setCount(getCount()); 0535 return startInsertMode(); 0536 } 0537 0538 /** 0539 * start insert mode after the last character of the line 0540 */ 0541 0542 bool NormalViMode::commandEnterInsertModeAppendEOL() 0543 { 0544 KTextEditor::Cursor c(m_view->cursorPosition()); 0545 c.setColumn(doc()->lineLength(c.line())); 0546 updateCursor(c); 0547 0548 m_stickyColumn = -1; 0549 m_viInputModeManager->getViInsertMode()->setCount(getCount()); 0550 return startInsertMode(); 0551 } 0552 0553 bool NormalViMode::commandEnterInsertModeBeforeFirstNonBlankInLine() 0554 { 0555 KTextEditor::Cursor cursor(m_view->cursorPosition()); 0556 int c = getFirstNonBlank(); 0557 0558 cursor.setColumn(c); 0559 updateCursor(cursor); 0560 0561 m_stickyColumn = -1; 0562 m_viInputModeManager->getViInsertMode()->setCount(getCount()); 0563 return startInsertMode(); 0564 } 0565 0566 /** 0567 * enter insert mode at the last insert position 0568 */ 0569 0570 bool NormalViMode::commandEnterInsertModeLast() 0571 { 0572 KTextEditor::Cursor c = m_viInputModeManager->marks()->getInsertStopped(); 0573 if (c.isValid()) { 0574 updateCursor(c); 0575 } 0576 0577 m_stickyColumn = -1; 0578 return startInsertMode(); 0579 } 0580 0581 bool NormalViMode::commandEnterVisualLineMode() 0582 { 0583 if (m_viInputModeManager->getCurrentViMode() == VisualLineMode) { 0584 reset(); 0585 return true; 0586 } 0587 0588 return startVisualLineMode(); 0589 } 0590 0591 bool NormalViMode::commandEnterVisualBlockMode() 0592 { 0593 if (m_viInputModeManager->getCurrentViMode() == VisualBlockMode) { 0594 reset(); 0595 return true; 0596 } 0597 0598 return startVisualBlockMode(); 0599 } 0600 0601 bool NormalViMode::commandReselectVisual() 0602 { 0603 // start last visual mode and set start = `< and cursor = `> 0604 KTextEditor::Cursor c1 = m_viInputModeManager->marks()->getSelectionStart(); 0605 KTextEditor::Cursor c2 = m_viInputModeManager->marks()->getSelectionFinish(); 0606 0607 // we should either get two valid cursors or two invalid cursors 0608 Q_ASSERT(c1.isValid() == c2.isValid()); 0609 0610 if (c1.isValid() && c2.isValid()) { 0611 m_viInputModeManager->getViVisualMode()->setStart(c1); 0612 bool returnValue = false; 0613 0614 switch (m_viInputModeManager->getViVisualMode()->getLastVisualMode()) { 0615 case ViMode::VisualMode: 0616 returnValue = commandEnterVisualMode(); 0617 break; 0618 case ViMode::VisualLineMode: 0619 returnValue = commandEnterVisualLineMode(); 0620 break; 0621 case ViMode::VisualBlockMode: 0622 returnValue = commandEnterVisualBlockMode(); 0623 break; 0624 default: 0625 Q_ASSERT("invalid visual mode"); 0626 } 0627 m_viInputModeManager->getViVisualMode()->goToPos(c2); 0628 return returnValue; 0629 } else { 0630 error(QStringLiteral("No previous visual selection")); 0631 } 0632 0633 return false; 0634 } 0635 0636 bool NormalViMode::commandEnterVisualMode() 0637 { 0638 if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualMode) { 0639 reset(); 0640 return true; 0641 } 0642 0643 return startVisualMode(); 0644 } 0645 0646 bool NormalViMode::commandToOtherEnd() 0647 { 0648 if (m_viInputModeManager->isAnyVisualMode()) { 0649 m_viInputModeManager->getViVisualMode()->switchStartEnd(); 0650 return true; 0651 } 0652 0653 return false; 0654 } 0655 0656 bool NormalViMode::commandEnterReplaceMode() 0657 { 0658 m_stickyColumn = -1; 0659 m_viInputModeManager->getViReplaceMode()->setCount(getCount()); 0660 return startReplaceMode(); 0661 } 0662 0663 bool NormalViMode::commandDeleteLine() 0664 { 0665 KTextEditor::Cursor c(m_view->cursorPosition()); 0666 0667 Range r; 0668 0669 r.startLine = c.line(); 0670 r.endLine = c.line() + getCount() - 1; 0671 0672 int column = c.column(); 0673 0674 bool ret = deleteRange(r, LineWise); 0675 0676 c = m_view->cursorPosition(); 0677 if (column > doc()->lineLength(c.line()) - 1) { 0678 column = doc()->lineLength(c.line()) - 1; 0679 } 0680 if (column < 0) { 0681 column = 0; 0682 } 0683 0684 if (c.line() > doc()->lines() - 1) { 0685 c.setLine(doc()->lines() - 1); 0686 } 0687 0688 c.setColumn(column); 0689 m_stickyColumn = -1; 0690 updateCursor(c); 0691 0692 m_deleteCommand = true; 0693 return ret; 0694 } 0695 0696 bool NormalViMode::commandDelete() 0697 { 0698 m_deleteCommand = true; 0699 return deleteRange(m_commandRange, getOperationMode()); 0700 } 0701 0702 bool NormalViMode::commandDeleteToEOL() 0703 { 0704 KTextEditor::Cursor c(m_view->cursorPosition()); 0705 OperationMode m = CharWise; 0706 0707 m_commandRange.endColumn = KateVi::EOL; 0708 switch (m_viInputModeManager->getCurrentViMode()) { 0709 case ViMode::NormalMode: 0710 m_commandRange.startLine = c.line(); 0711 m_commandRange.startColumn = c.column(); 0712 m_commandRange.endLine = c.line() + getCount() - 1; 0713 break; 0714 case ViMode::VisualMode: 0715 case ViMode::VisualLineMode: 0716 m = LineWise; 0717 break; 0718 case ViMode::VisualBlockMode: 0719 m_commandRange.normalize(); 0720 m = Block; 0721 break; 0722 default: 0723 /* InsertMode and ReplaceMode will never call this method. */ 0724 Q_ASSERT(false); 0725 } 0726 0727 bool r = deleteRange(m_commandRange, m); 0728 0729 switch (m) { 0730 case CharWise: 0731 c.setColumn(doc()->lineLength(c.line()) - 1); 0732 break; 0733 case LineWise: 0734 c.setLine(m_commandRange.startLine); 0735 c.setColumn(getFirstNonBlank(qMin(doc()->lastLine(), m_commandRange.startLine))); 0736 break; 0737 case Block: 0738 c.setLine(m_commandRange.startLine); 0739 c.setColumn(m_commandRange.startColumn - 1); 0740 break; 0741 } 0742 0743 // make sure cursor position is valid after deletion 0744 if (c.line() < 0) { 0745 c.setLine(0); 0746 } 0747 if (c.line() > doc()->lastLine()) { 0748 c.setLine(doc()->lastLine()); 0749 } 0750 if (c.column() > doc()->lineLength(c.line()) - 1) { 0751 c.setColumn(doc()->lineLength(c.line()) - 1); 0752 } 0753 if (c.column() < 0) { 0754 c.setColumn(0); 0755 } 0756 0757 updateCursor(c); 0758 0759 m_deleteCommand = true; 0760 return r; 0761 } 0762 0763 bool NormalViMode::commandMakeLowercase() 0764 { 0765 KTextEditor::Cursor c = m_view->cursorPosition(); 0766 0767 OperationMode m = getOperationMode(); 0768 QString text = getRange(m_commandRange, m); 0769 if (m == LineWise) { 0770 text.chop(1); // don't need '\n' at the end; 0771 } 0772 QString lowerCase = text.toLower(); 0773 0774 m_commandRange.normalize(); 0775 KTextEditor::Cursor start(m_commandRange.startLine, m_commandRange.startColumn); 0776 KTextEditor::Cursor end(m_commandRange.endLine, m_commandRange.endColumn); 0777 KTextEditor::Range range(start, end); 0778 0779 doc()->replaceText(range, lowerCase, m == Block); 0780 0781 if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) { 0782 updateCursor(start); 0783 } else { 0784 updateCursor(c); 0785 } 0786 0787 return true; 0788 } 0789 0790 bool NormalViMode::commandMakeLowercaseLine() 0791 { 0792 KTextEditor::Cursor c(m_view->cursorPosition()); 0793 0794 if (doc()->lineLength(c.line()) == 0) { 0795 // Nothing to do. 0796 return true; 0797 } 0798 0799 m_commandRange.startLine = c.line(); 0800 m_commandRange.endLine = c.line() + getCount() - 1; 0801 m_commandRange.startColumn = 0; 0802 m_commandRange.endColumn = doc()->lineLength(c.line()) - 1; 0803 0804 return commandMakeLowercase(); 0805 } 0806 0807 bool NormalViMode::commandMakeUppercase() 0808 { 0809 if (!m_commandRange.valid) { 0810 return false; 0811 } 0812 KTextEditor::Cursor c = m_view->cursorPosition(); 0813 OperationMode m = getOperationMode(); 0814 QString text = getRange(m_commandRange, m); 0815 if (m == LineWise) { 0816 text.chop(1); // don't need '\n' at the end; 0817 } 0818 QString upperCase = text.toUpper(); 0819 0820 m_commandRange.normalize(); 0821 KTextEditor::Cursor start(m_commandRange.startLine, m_commandRange.startColumn); 0822 KTextEditor::Cursor end(m_commandRange.endLine, m_commandRange.endColumn); 0823 KTextEditor::Range range(start, end); 0824 0825 doc()->replaceText(range, upperCase, m == Block); 0826 if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) { 0827 updateCursor(start); 0828 } else { 0829 updateCursor(c); 0830 } 0831 0832 return true; 0833 } 0834 0835 bool NormalViMode::commandMakeUppercaseLine() 0836 { 0837 KTextEditor::Cursor c(m_view->cursorPosition()); 0838 0839 if (doc()->lineLength(c.line()) == 0) { 0840 // Nothing to do. 0841 return true; 0842 } 0843 0844 m_commandRange.startLine = c.line(); 0845 m_commandRange.endLine = c.line() + getCount() - 1; 0846 m_commandRange.startColumn = 0; 0847 m_commandRange.endColumn = doc()->lineLength(c.line()) - 1; 0848 0849 return commandMakeUppercase(); 0850 } 0851 0852 bool NormalViMode::commandChangeCase() 0853 { 0854 QString text; 0855 KTextEditor::Range range; 0856 KTextEditor::Cursor c(m_view->cursorPosition()); 0857 0858 // in visual mode, the range is from start position to end position... 0859 if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualMode || m_viInputModeManager->getCurrentViMode() == ViMode::VisualBlockMode) { 0860 KTextEditor::Cursor c2 = m_viInputModeManager->getViVisualMode()->getStart(); 0861 0862 if (c2 > c) { 0863 c2.setColumn(c2.column() + 1); 0864 } else { 0865 c.setColumn(c.column() + 1); 0866 } 0867 0868 range.setRange(c, c2); 0869 // ... in visual line mode, the range is from column 0 on the first line to 0870 // the line length of the last line... 0871 } else if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualLineMode) { 0872 KTextEditor::Cursor c2 = m_viInputModeManager->getViVisualMode()->getStart(); 0873 0874 if (c2 > c) { 0875 c2.setColumn(doc()->lineLength(c2.line())); 0876 c.setColumn(0); 0877 } else { 0878 c.setColumn(doc()->lineLength(c.line())); 0879 c2.setColumn(0); 0880 } 0881 0882 range.setRange(c, c2); 0883 // ... and in normal mode the range is from the current position to the 0884 // current position + count 0885 } else { 0886 KTextEditor::Cursor c2 = c; 0887 c2.setColumn(c.column() + getCount()); 0888 0889 if (c2.column() > doc()->lineLength(c.line())) { 0890 c2.setColumn(doc()->lineLength(c.line())); 0891 } 0892 0893 range.setRange(c, c2); 0894 } 0895 0896 bool block = m_viInputModeManager->getCurrentViMode() == ViMode::VisualBlockMode; 0897 0898 // get the text the command should operate on 0899 text = doc()->text(range, block); 0900 0901 // for every character, switch its case 0902 for (int i = 0; i < text.length(); i++) { 0903 if (text.at(i).isUpper()) { 0904 text[i] = text.at(i).toLower(); 0905 } else if (text.at(i).isLower()) { 0906 text[i] = text.at(i).toUpper(); 0907 } 0908 } 0909 0910 // replace the old text with the modified text 0911 doc()->replaceText(range, text, block); 0912 0913 // in normal mode, move the cursor to the right, in visual mode move the 0914 // cursor to the start of the selection 0915 if (m_viInputModeManager->getCurrentViMode() == ViMode::NormalMode) { 0916 updateCursor(range.end()); 0917 } else { 0918 updateCursor(range.start()); 0919 } 0920 0921 return true; 0922 } 0923 0924 bool NormalViMode::commandChangeCaseRange() 0925 { 0926 OperationMode m = getOperationMode(); 0927 QString changedCase = getRange(m_commandRange, m); 0928 if (m == LineWise) { 0929 changedCase.chop(1); // don't need '\n' at the end; 0930 } 0931 KTextEditor::Range range = KTextEditor::Range(m_commandRange.startLine, m_commandRange.startColumn, m_commandRange.endLine, m_commandRange.endColumn); 0932 // get the text the command should operate on 0933 // for every character, switch its case 0934 for (int i = 0; i < changedCase.length(); i++) { 0935 if (changedCase.at(i).isUpper()) { 0936 changedCase[i] = changedCase.at(i).toLower(); 0937 } else if (changedCase.at(i).isLower()) { 0938 changedCase[i] = changedCase.at(i).toUpper(); 0939 } 0940 } 0941 doc()->replaceText(range, changedCase, m == Block); 0942 return true; 0943 } 0944 0945 bool NormalViMode::commandChangeCaseLine() 0946 { 0947 KTextEditor::Cursor c(m_view->cursorPosition()); 0948 0949 if (doc()->lineLength(c.line()) == 0) { 0950 // Nothing to do. 0951 return true; 0952 } 0953 0954 m_commandRange.startLine = c.line(); 0955 m_commandRange.endLine = c.line() + getCount() - 1; 0956 m_commandRange.startColumn = 0; 0957 m_commandRange.endColumn = doc()->lineLength(c.line()) - 1; // -1 is for excluding '\0' 0958 0959 if (!commandChangeCaseRange()) { 0960 return false; 0961 } 0962 0963 KTextEditor::Cursor start(m_commandRange.startLine, m_commandRange.startColumn); 0964 if (getCount() > 1) { 0965 updateCursor(c); 0966 } else { 0967 updateCursor(start); 0968 } 0969 return true; 0970 } 0971 0972 bool NormalViMode::commandOpenNewLineUnder() 0973 { 0974 doc()->setUndoMergeAllEdits(true); 0975 0976 KTextEditor::Cursor c(m_view->cursorPosition()); 0977 0978 c.setColumn(doc()->lineLength(c.line())); 0979 updateCursor(c); 0980 0981 doc()->newLine(m_view); 0982 0983 m_stickyColumn = -1; 0984 startInsertMode(); 0985 m_viInputModeManager->getViInsertMode()->setCount(getCount()); 0986 m_viInputModeManager->getViInsertMode()->setCountedRepeatsBeginOnNewLine(true); 0987 0988 return true; 0989 } 0990 0991 bool NormalViMode::commandOpenNewLineOver() 0992 { 0993 doc()->setUndoMergeAllEdits(true); 0994 0995 KTextEditor::Cursor c(m_view->cursorPosition()); 0996 0997 if (c.line() == 0) { 0998 doc()->insertLine(0, QString()); 0999 c.setColumn(0); 1000 c.setLine(0); 1001 updateCursor(c); 1002 } else { 1003 c.setLine(c.line() - 1); 1004 c.setColumn(getLine(c.line()).length()); 1005 updateCursor(c); 1006 doc()->newLine(m_view); 1007 } 1008 1009 m_stickyColumn = -1; 1010 startInsertMode(); 1011 m_viInputModeManager->getViInsertMode()->setCount(getCount()); 1012 m_viInputModeManager->getViInsertMode()->setCountedRepeatsBeginOnNewLine(true); 1013 1014 return true; 1015 } 1016 1017 bool NormalViMode::commandJoinLines() 1018 { 1019 KTextEditor::Cursor c(m_view->cursorPosition()); 1020 1021 unsigned int from = c.line(); 1022 unsigned int to = c.line() + ((getCount() == 1) ? 1 : getCount() - 1); 1023 1024 // if we were given a range of lines, this information overrides the previous 1025 if (m_commandRange.startLine != -1 && m_commandRange.endLine != -1) { 1026 m_commandRange.normalize(); 1027 c.setLine(m_commandRange.startLine); 1028 from = m_commandRange.startLine; 1029 to = m_commandRange.endLine; 1030 } 1031 1032 if (to >= (unsigned int)doc()->lines()) { 1033 return false; 1034 } 1035 1036 bool nonEmptyLineFound = false; 1037 for (unsigned int lineNum = from; lineNum <= to; lineNum++) { 1038 if (!doc()->line(lineNum).isEmpty()) { 1039 nonEmptyLineFound = true; 1040 } 1041 } 1042 1043 const int firstNonWhitespaceOnLastLine = doc()->kateTextLine(to).firstChar(); 1044 QString leftTrimmedLastLine; 1045 if (firstNonWhitespaceOnLastLine != -1) { 1046 leftTrimmedLastLine = doc()->line(to).mid(firstNonWhitespaceOnLastLine); 1047 } 1048 1049 joinLines(from, to); 1050 1051 if (nonEmptyLineFound && leftTrimmedLastLine.isEmpty()) { 1052 // joinLines won't have added a trailing " ", whereas Vim does - follow suit. 1053 doc()->insertText(KTextEditor::Cursor(from, doc()->lineLength(from)), QStringLiteral(" ")); 1054 } 1055 1056 // Position cursor just before first non-whitesspace character of what was the last line joined. 1057 c.setColumn(doc()->lineLength(from) - leftTrimmedLastLine.length() - 1); 1058 if (c.column() >= 0) { 1059 updateCursor(c); 1060 } 1061 1062 m_deleteCommand = true; 1063 return true; 1064 } 1065 1066 bool NormalViMode::commandChange() 1067 { 1068 KTextEditor::Cursor c(m_view->cursorPosition()); 1069 1070 OperationMode m = getOperationMode(); 1071 1072 doc()->setUndoMergeAllEdits(true); 1073 1074 commandDelete(); 1075 1076 if (m == LineWise) { 1077 // if we deleted several lines, insert an empty line and put the cursor there. 1078 doc()->insertLine(m_commandRange.startLine, QString()); 1079 c.setLine(m_commandRange.startLine); 1080 c.setColumn(0); 1081 } else if (m == Block) { 1082 // block substitute can be simulated by first deleting the text 1083 // (done above) and then starting block prepend. 1084 return commandPrependToBlock(); 1085 } else { 1086 if (m_commandRange.startLine < m_commandRange.endLine) { 1087 c.setLine(m_commandRange.startLine); 1088 } 1089 c.setColumn(m_commandRange.startColumn); 1090 } 1091 1092 updateCursor(c); 1093 setCount(0); // The count was for the motion, not the insertion. 1094 commandEnterInsertMode(); 1095 1096 // correct indentation level 1097 if (m == LineWise) { 1098 m_view->align(); 1099 } 1100 1101 m_deleteCommand = true; 1102 return true; 1103 } 1104 1105 bool NormalViMode::commandChangeToEOL() 1106 { 1107 commandDeleteToEOL(); 1108 1109 if (getOperationMode() == Block) { 1110 return commandPrependToBlock(); 1111 } 1112 1113 m_deleteCommand = true; 1114 return commandEnterInsertModeAppend(); 1115 } 1116 1117 bool NormalViMode::commandChangeLine() 1118 { 1119 m_deleteCommand = true; 1120 KTextEditor::Cursor c(m_view->cursorPosition()); 1121 c.setColumn(0); 1122 updateCursor(c); 1123 1124 doc()->setUndoMergeAllEdits(true); 1125 1126 // if count >= 2 start by deleting the whole lines 1127 if (getCount() >= 2) { 1128 Range r(c.line(), 0, c.line() + getCount() - 2, 0, InclusiveMotion); 1129 deleteRange(r); 1130 } 1131 1132 // ... then delete the _contents_ of the last line, but keep the line 1133 Range r(c.line(), c.column(), c.line(), doc()->lineLength(c.line()) - 1, InclusiveMotion); 1134 deleteRange(r, CharWise, true); 1135 1136 // ... then enter insert mode 1137 if (getOperationMode() == Block) { 1138 return commandPrependToBlock(); 1139 } 1140 commandEnterInsertModeAppend(); 1141 1142 // correct indentation level 1143 m_view->align(); 1144 1145 return true; 1146 } 1147 1148 bool NormalViMode::commandSubstituteChar() 1149 { 1150 if (commandDeleteChar()) { 1151 // The count is only used for deletion of chars; the inserted text is not repeated 1152 setCount(0); 1153 return commandEnterInsertMode(); 1154 } 1155 1156 m_deleteCommand = true; 1157 return false; 1158 } 1159 1160 bool NormalViMode::commandSubstituteLine() 1161 { 1162 m_deleteCommand = true; 1163 return commandChangeLine(); 1164 } 1165 1166 bool NormalViMode::commandYank() 1167 { 1168 bool r = false; 1169 QString yankedText; 1170 1171 OperationMode m = getOperationMode(); 1172 yankedText = getRange(m_commandRange, m); 1173 1174 highlightYank(m_commandRange, m); 1175 1176 QChar chosen_register = getChosenRegister(ZeroRegister); 1177 fillRegister(chosen_register, yankedText, m); 1178 yankToClipBoard(chosen_register, yankedText); 1179 1180 return r; 1181 } 1182 1183 bool NormalViMode::commandYankLine() 1184 { 1185 KTextEditor::Cursor c(m_view->cursorPosition()); 1186 QString lines; 1187 int linenum = c.line(); 1188 1189 for (int i = 0; i < getCount(); i++) { 1190 lines.append(getLine(linenum + i) + QLatin1Char('\n')); 1191 } 1192 1193 Range yankRange(linenum, 0, linenum + getCount() - 1, getLine(linenum + getCount() - 1).length(), InclusiveMotion); 1194 highlightYank(yankRange); 1195 1196 QChar chosen_register = getChosenRegister(ZeroRegister); 1197 fillRegister(chosen_register, lines, LineWise); 1198 yankToClipBoard(chosen_register, lines); 1199 1200 return true; 1201 } 1202 1203 bool NormalViMode::commandYankToEOL() 1204 { 1205 OperationMode m = CharWise; 1206 KTextEditor::Cursor c(m_view->cursorPosition()); 1207 1208 MotionType motion = m_commandRange.motionType; 1209 m_commandRange.endLine = c.line() + getCount() - 1; 1210 m_commandRange.endColumn = doc()->lineLength(m_commandRange.endLine) - 1; 1211 m_commandRange.motionType = InclusiveMotion; 1212 1213 switch (m_viInputModeManager->getCurrentViMode()) { 1214 case ViMode::NormalMode: 1215 m_commandRange.startLine = c.line(); 1216 m_commandRange.startColumn = c.column(); 1217 break; 1218 case ViMode::VisualMode: 1219 case ViMode::VisualLineMode: 1220 m = LineWise; 1221 { 1222 VisualViMode *visual = static_cast<VisualViMode *>(this); 1223 visual->setStart(KTextEditor::Cursor(visual->getStart().line(), 0)); 1224 } 1225 break; 1226 case ViMode::VisualBlockMode: 1227 m = Block; 1228 break; 1229 default: 1230 /* InsertMode and ReplaceMode will never call this method. */ 1231 Q_ASSERT(false); 1232 } 1233 1234 const QString &yankedText = getRange(m_commandRange, m); 1235 m_commandRange.motionType = motion; 1236 highlightYank(m_commandRange); 1237 1238 QChar chosen_register = getChosenRegister(ZeroRegister); 1239 fillRegister(chosen_register, yankedText, m); 1240 yankToClipBoard(chosen_register, yankedText); 1241 1242 return true; 1243 } 1244 1245 // Insert the text in the given register after the cursor position. 1246 // This is the non-g version of paste, so the cursor will usually 1247 // end up on the last character of the pasted text, unless the text 1248 // was multi-line or linewise in which case it will end up 1249 // on the *first* character of the pasted text(!) 1250 // If linewise, will paste after the current line. 1251 bool NormalViMode::commandPaste() 1252 { 1253 return paste(AfterCurrentPosition, false, false); 1254 } 1255 1256 // As with commandPaste, except that the text is pasted *at* the cursor position 1257 bool NormalViMode::commandPasteBefore() 1258 { 1259 return paste(AtCurrentPosition, false, false); 1260 } 1261 1262 // As with commandPaste, except that the cursor will generally be placed *after* the 1263 // last pasted character (assuming the last pasted character is not at the end of the line). 1264 // If linewise, cursor will be at the beginning of the line *after* the last line of pasted text, 1265 // unless that line is the last line of the document; then it will be placed at the beginning of the 1266 // last line pasted. 1267 bool NormalViMode::commandgPaste() 1268 { 1269 return paste(AfterCurrentPosition, true, false); 1270 } 1271 1272 // As with commandgPaste, except that it pastes *at* the current cursor position or, if linewise, 1273 // at the current line. 1274 bool NormalViMode::commandgPasteBefore() 1275 { 1276 return paste(AtCurrentPosition, true, false); 1277 } 1278 1279 bool NormalViMode::commandIndentedPaste() 1280 { 1281 return paste(AfterCurrentPosition, false, true); 1282 } 1283 1284 bool NormalViMode::commandIndentedPasteBefore() 1285 { 1286 return paste(AtCurrentPosition, false, true); 1287 } 1288 1289 bool NormalViMode::commandDeleteChar() 1290 { 1291 KTextEditor::Cursor c(m_view->cursorPosition()); 1292 Range r(c.line(), c.column(), c.line(), c.column() + getCount(), ExclusiveMotion); 1293 1294 if (m_commandRange.startLine != -1 && m_commandRange.startColumn != -1) { 1295 r = m_commandRange; 1296 } else { 1297 if (r.endColumn > doc()->lineLength(r.startLine)) { 1298 r.endColumn = doc()->lineLength(r.startLine); 1299 } 1300 } 1301 1302 // should delete entire lines if in visual line mode and selection in visual block mode 1303 OperationMode m = CharWise; 1304 if (m_viInputModeManager->getCurrentViMode() == VisualLineMode) { 1305 m = LineWise; 1306 } else if (m_viInputModeManager->getCurrentViMode() == VisualBlockMode) { 1307 m = Block; 1308 } 1309 1310 m_deleteCommand = true; 1311 return deleteRange(r, m); 1312 } 1313 1314 bool NormalViMode::commandDeleteCharBackward() 1315 { 1316 KTextEditor::Cursor c(m_view->cursorPosition()); 1317 1318 Range r(c.line(), c.column() - getCount(), c.line(), c.column(), ExclusiveMotion); 1319 1320 if (m_commandRange.startLine != -1 && m_commandRange.startColumn != -1) { 1321 r = m_commandRange; 1322 } else { 1323 if (r.startColumn < 0) { 1324 r.startColumn = 0; 1325 } 1326 } 1327 1328 // should delete entire lines if in visual line mode and selection in visual block mode 1329 OperationMode m = CharWise; 1330 if (m_viInputModeManager->getCurrentViMode() == VisualLineMode) { 1331 m = LineWise; 1332 } else if (m_viInputModeManager->getCurrentViMode() == VisualBlockMode) { 1333 m = Block; 1334 } 1335 1336 m_deleteCommand = true; 1337 return deleteRange(r, m); 1338 } 1339 1340 bool NormalViMode::commandReplaceCharacter() 1341 { 1342 QString key = KeyParser::self()->decodeKeySequence(m_keys.right(1)); 1343 1344 // Filter out some special keys. 1345 const int keyCode = KeyParser::self()->encoded2qt(m_keys.right(1)); 1346 switch (keyCode) { 1347 case Qt::Key_Left: 1348 case Qt::Key_Right: 1349 case Qt::Key_Up: 1350 case Qt::Key_Down: 1351 case Qt::Key_Home: 1352 case Qt::Key_End: 1353 case Qt::Key_PageUp: 1354 case Qt::Key_PageDown: 1355 case Qt::Key_Delete: 1356 case Qt::Key_Insert: 1357 case Qt::Key_Backspace: 1358 case Qt::Key_CapsLock: 1359 return true; 1360 case Qt::Key_Return: 1361 case Qt::Key_Enter: 1362 key = QStringLiteral("\n"); 1363 } 1364 1365 bool r; 1366 if (m_viInputModeManager->isAnyVisualMode()) { 1367 OperationMode m = getOperationMode(); 1368 QString text = getRange(m_commandRange, m); 1369 1370 if (m == LineWise) { 1371 text.chop(1); // don't need '\n' at the end; 1372 } 1373 1374 static const QRegularExpression nonNewlineRegex(QStringLiteral("[^\n]")); 1375 text.replace(nonNewlineRegex, key); 1376 1377 m_commandRange.normalize(); 1378 KTextEditor::Cursor start(m_commandRange.startLine, m_commandRange.startColumn); 1379 KTextEditor::Cursor end(m_commandRange.endLine, m_commandRange.endColumn); 1380 KTextEditor::Range range(start, end); 1381 1382 r = doc()->replaceText(range, text, m == Block); 1383 1384 } else { 1385 KTextEditor::Cursor c1(m_view->cursorPosition()); 1386 KTextEditor::Cursor c2(m_view->cursorPosition()); 1387 1388 c2.setColumn(c2.column() + getCount()); 1389 1390 if (c2.column() > doc()->lineLength(m_view->cursorPosition().line())) { 1391 return false; 1392 } 1393 1394 r = doc()->replaceText(KTextEditor::Range(c1, c2), key.repeated(getCount())); 1395 updateCursor(c1); 1396 } 1397 return r; 1398 } 1399 1400 bool NormalViMode::commandSwitchToCmdLine() 1401 { 1402 QString initialText; 1403 if (m_viInputModeManager->isAnyVisualMode()) { 1404 // if in visual mode, make command range == visual selection 1405 m_viInputModeManager->getViVisualMode()->saveRangeMarks(); 1406 initialText = QStringLiteral("'<,'>"); 1407 } else if (getCount() != 1) { 1408 // if a count is given, the range [current line] to [current line] + 1409 // count should be prepended to the command line 1410 initialText = QLatin1String(".,.+") + QString::number(getCount() - 1); 1411 } 1412 1413 m_viInputModeManager->inputAdapter()->showViModeEmulatedCommandBar(); 1414 m_viInputModeManager->inputAdapter()->viModeEmulatedCommandBar()->init(EmulatedCommandBar::Command, initialText); 1415 1416 m_commandShouldKeepSelection = true; 1417 1418 return true; 1419 } 1420 1421 bool NormalViMode::commandSearchBackward() 1422 { 1423 m_viInputModeManager->inputAdapter()->showViModeEmulatedCommandBar(); 1424 m_viInputModeManager->inputAdapter()->viModeEmulatedCommandBar()->init(EmulatedCommandBar::SearchBackward); 1425 return true; 1426 } 1427 1428 bool NormalViMode::commandSearchForward() 1429 { 1430 m_viInputModeManager->inputAdapter()->showViModeEmulatedCommandBar(); 1431 m_viInputModeManager->inputAdapter()->viModeEmulatedCommandBar()->init(EmulatedCommandBar::SearchForward); 1432 return true; 1433 } 1434 1435 bool NormalViMode::commandUndo() 1436 { 1437 // See BUG #328277 1438 m_viInputModeManager->clearCurrentChangeLog(); 1439 1440 if (doc()->undoCount() > 0) { 1441 const bool mapped = m_viInputModeManager->keyMapper()->isExecutingMapping(); 1442 1443 if (mapped) { 1444 doc()->editEnd(); 1445 } 1446 doc()->undo(); 1447 if (mapped) { 1448 doc()->editStart(); 1449 } 1450 if (m_viInputModeManager->isAnyVisualMode()) { 1451 m_viInputModeManager->getViVisualMode()->setStart(KTextEditor::Cursor(-1, -1)); 1452 m_view->clearSelection(); 1453 startNormalMode(); 1454 } 1455 return true; 1456 } 1457 return false; 1458 } 1459 1460 bool NormalViMode::commandRedo() 1461 { 1462 if (doc()->redoCount() > 0) { 1463 const bool mapped = m_viInputModeManager->keyMapper()->isExecutingMapping(); 1464 1465 if (mapped) { 1466 doc()->editEnd(); 1467 } 1468 doc()->redo(); 1469 if (mapped) { 1470 doc()->editStart(); 1471 } 1472 if (m_viInputModeManager->isAnyVisualMode()) { 1473 m_viInputModeManager->getViVisualMode()->setStart(KTextEditor::Cursor(-1, -1)); 1474 m_view->clearSelection(); 1475 startNormalMode(); 1476 } 1477 return true; 1478 } 1479 return false; 1480 } 1481 1482 bool NormalViMode::commandSetMark() 1483 { 1484 KTextEditor::Cursor c(m_view->cursorPosition()); 1485 1486 QChar mark = m_keys.at(m_keys.size() - 1); 1487 m_viInputModeManager->marks()->setUserMark(mark, c); 1488 1489 return true; 1490 } 1491 1492 bool NormalViMode::commandIndentLine() 1493 { 1494 KTextEditor::Cursor c(m_view->cursorPosition()); 1495 1496 doc()->indent(KTextEditor::Range(c.line(), 0, c.line() + getCount(), 0), 1); 1497 1498 return true; 1499 } 1500 1501 bool NormalViMode::commandUnindentLine() 1502 { 1503 KTextEditor::Cursor c(m_view->cursorPosition()); 1504 1505 doc()->indent(KTextEditor::Range(c.line(), 0, c.line() + getCount(), 0), -1); 1506 1507 return true; 1508 } 1509 1510 bool NormalViMode::commandIndentLines() 1511 { 1512 const bool downwards = m_commandRange.startLine < m_commandRange.endLine; 1513 1514 m_commandRange.normalize(); 1515 1516 int line1 = m_commandRange.startLine; 1517 int line2 = m_commandRange.endLine; 1518 int col = getLine(line2).length(); 1519 doc()->indent(KTextEditor::Range(line1, 0, line2, col), getCount()); 1520 1521 if (downwards) { 1522 updateCursor(KTextEditor::Cursor(m_commandRange.startLine, m_commandRange.startColumn)); 1523 } else { 1524 updateCursor(KTextEditor::Cursor(m_commandRange.endLine, m_commandRange.endColumn)); 1525 } 1526 return true; 1527 } 1528 1529 bool NormalViMode::commandUnindentLines() 1530 { 1531 const bool downwards = m_commandRange.startLine < m_commandRange.endLine; 1532 1533 m_commandRange.normalize(); 1534 1535 int line1 = m_commandRange.startLine; 1536 int line2 = m_commandRange.endLine; 1537 1538 doc()->indent(KTextEditor::Range(line1, 0, line2, doc()->lineLength(line2)), -getCount()); 1539 1540 if (downwards) { 1541 updateCursor(KTextEditor::Cursor(m_commandRange.startLine, m_commandRange.startColumn)); 1542 } else { 1543 updateCursor(KTextEditor::Cursor(m_commandRange.endLine, m_commandRange.endColumn)); 1544 } 1545 return true; 1546 } 1547 1548 bool NormalViMode::commandScrollPageDown() 1549 { 1550 if (getCount() < m_scroll_count_limit) { 1551 for (int i = 0; i < getCount(); i++) { 1552 m_view->pageDown(); 1553 } 1554 } 1555 return true; 1556 } 1557 1558 bool NormalViMode::commandScrollPageUp() 1559 { 1560 if (getCount() < m_scroll_count_limit) { 1561 for (int i = 0; i < getCount(); i++) { 1562 m_view->pageUp(); 1563 } 1564 } 1565 return true; 1566 } 1567 1568 bool NormalViMode::commandScrollHalfPageUp() 1569 { 1570 if (getCount() < m_scroll_count_limit) { 1571 for (int i = 0; i < getCount(); i++) { 1572 m_viewInternal->pageUp(false, true); 1573 } 1574 } 1575 return true; 1576 } 1577 1578 bool NormalViMode::commandScrollHalfPageDown() 1579 { 1580 if (getCount() < m_scroll_count_limit) { 1581 for (int i = 0; i < getCount(); i++) { 1582 m_viewInternal->pageDown(false, true); 1583 } 1584 } 1585 return true; 1586 } 1587 1588 bool NormalViMode::commandCenterView(bool onFirst) 1589 { 1590 KTextEditor::Cursor c(m_view->cursorPosition()); 1591 const int virtualCenterLine = m_viewInternal->startLine() + linesDisplayed() / 2; 1592 const int virtualCursorLine = m_view->textFolding().lineToVisibleLine(c.line()); 1593 1594 scrollViewLines(virtualCursorLine - virtualCenterLine); 1595 if (onFirst) { 1596 c.setColumn(getFirstNonBlank()); 1597 updateCursor(c); 1598 } 1599 return true; 1600 } 1601 1602 bool NormalViMode::commandCenterViewOnNonBlank() 1603 { 1604 return commandCenterView(true); 1605 } 1606 1607 bool NormalViMode::commandCenterViewOnCursor() 1608 { 1609 return commandCenterView(false); 1610 } 1611 1612 bool NormalViMode::commandTopView(bool onFirst) 1613 { 1614 KTextEditor::Cursor c(m_view->cursorPosition()); 1615 const int virtualCenterLine = m_viewInternal->startLine(); 1616 const int virtualCursorLine = m_view->textFolding().lineToVisibleLine(c.line()); 1617 1618 scrollViewLines(virtualCursorLine - virtualCenterLine); 1619 if (onFirst) { 1620 c.setColumn(getFirstNonBlank()); 1621 updateCursor(c); 1622 } 1623 return true; 1624 } 1625 1626 bool NormalViMode::commandTopViewOnNonBlank() 1627 { 1628 return commandTopView(true); 1629 } 1630 1631 bool NormalViMode::commandTopViewOnCursor() 1632 { 1633 return commandTopView(false); 1634 } 1635 1636 bool NormalViMode::commandBottomView(bool onFirst) 1637 { 1638 KTextEditor::Cursor c(m_view->cursorPosition()); 1639 const int virtualCenterLine = m_viewInternal->endLine(); 1640 const int virtualCursorLine = m_view->textFolding().lineToVisibleLine(c.line()); 1641 1642 scrollViewLines(virtualCursorLine - virtualCenterLine); 1643 if (onFirst) { 1644 c.setColumn(getFirstNonBlank()); 1645 updateCursor(c); 1646 } 1647 return true; 1648 } 1649 1650 bool NormalViMode::commandBottomViewOnNonBlank() 1651 { 1652 return commandBottomView(true); 1653 } 1654 1655 bool NormalViMode::commandBottomViewOnCursor() 1656 { 1657 return commandBottomView(false); 1658 } 1659 1660 bool NormalViMode::commandAbort() 1661 { 1662 m_pendingResetIsDueToExit = true; 1663 reset(); 1664 return true; 1665 } 1666 1667 bool NormalViMode::commandPrintCharacterCode() 1668 { 1669 QChar ch = getCharUnderCursor(); 1670 1671 if (ch == QChar::Null) { 1672 message(QStringLiteral("NUL")); 1673 } else { 1674 int code = ch.unicode(); 1675 1676 QString dec = QString::number(code); 1677 QString hex = QString::number(code, 16); 1678 QString oct = QString::number(code, 8); 1679 if (oct.length() < 3) { 1680 oct.prepend(QLatin1Char('0')); 1681 } 1682 if (code > 0x80 && code < 0x1000) { 1683 hex.prepend((code < 0x100 ? QLatin1String("00") : QLatin1String("0"))); 1684 } 1685 message(i18n("'%1' %2, Hex %3, Octal %4", ch, dec, hex, oct)); 1686 } 1687 1688 return true; 1689 } 1690 1691 bool NormalViMode::commandRepeatLastChange() 1692 { 1693 const int repeatCount = getCount(); 1694 resetParser(); 1695 if (repeatCount > 1) { 1696 m_oneTimeCountOverride = repeatCount; 1697 } 1698 doc()->editStart(); 1699 m_viInputModeManager->repeatLastChange(); 1700 doc()->editEnd(); 1701 1702 return true; 1703 } 1704 1705 bool NormalViMode::commandAlignLine() 1706 { 1707 const int line = m_view->cursorPosition().line(); 1708 KTextEditor::Range alignRange(KTextEditor::Cursor(line, 0), KTextEditor::Cursor(line, 0)); 1709 1710 doc()->align(m_view, alignRange); 1711 1712 return true; 1713 } 1714 1715 bool NormalViMode::commandAlignLines() 1716 { 1717 m_commandRange.normalize(); 1718 1719 KTextEditor::Cursor start(m_commandRange.startLine, 0); 1720 KTextEditor::Cursor end(m_commandRange.endLine, 0); 1721 1722 doc()->align(m_view, KTextEditor::Range(start, end)); 1723 1724 return true; 1725 } 1726 1727 bool NormalViMode::commandAddToNumber() 1728 { 1729 addToNumberUnderCursor(getCount()); 1730 1731 return true; 1732 } 1733 1734 bool NormalViMode::commandSubtractFromNumber() 1735 { 1736 addToNumberUnderCursor(-getCount()); 1737 1738 return true; 1739 } 1740 1741 bool NormalViMode::commandPrependToBlock() 1742 { 1743 KTextEditor::Cursor c(m_view->cursorPosition()); 1744 1745 // move cursor to top left corner of selection 1746 m_commandRange.normalize(); 1747 c.setColumn(m_commandRange.startColumn); 1748 c.setLine(m_commandRange.startLine); 1749 updateCursor(c); 1750 1751 m_stickyColumn = -1; 1752 m_viInputModeManager->getViInsertMode()->setBlockPrependMode(m_commandRange); 1753 return startInsertMode(); 1754 } 1755 1756 bool NormalViMode::commandAppendToBlock() 1757 { 1758 KTextEditor::Cursor c(m_view->cursorPosition()); 1759 1760 m_commandRange.normalize(); 1761 if (m_stickyColumn == (unsigned int)KateVi::EOL) { // append to EOL 1762 // move cursor to end of first line 1763 c.setLine(m_commandRange.startLine); 1764 c.setColumn(doc()->lineLength(c.line())); 1765 updateCursor(c); 1766 m_viInputModeManager->getViInsertMode()->setBlockAppendMode(m_commandRange, AppendEOL); 1767 } else { 1768 m_viInputModeManager->getViInsertMode()->setBlockAppendMode(m_commandRange, Append); 1769 // move cursor to top right corner of selection 1770 c.setColumn(m_commandRange.endColumn + 1); 1771 c.setLine(m_commandRange.startLine); 1772 updateCursor(c); 1773 } 1774 1775 m_stickyColumn = -1; 1776 1777 return startInsertMode(); 1778 } 1779 1780 bool NormalViMode::commandGoToNextJump() 1781 { 1782 KTextEditor::Cursor c = getNextJump(m_view->cursorPosition()); 1783 updateCursor(c); 1784 1785 return true; 1786 } 1787 1788 bool NormalViMode::commandGoToPrevJump() 1789 { 1790 KTextEditor::Cursor c = getPrevJump(m_view->cursorPosition()); 1791 updateCursor(c); 1792 1793 return true; 1794 } 1795 1796 bool NormalViMode::commandSwitchToLeftView() 1797 { 1798 switchView(Left); 1799 return true; 1800 } 1801 1802 bool NormalViMode::commandSwitchToDownView() 1803 { 1804 switchView(Down); 1805 return true; 1806 } 1807 1808 bool NormalViMode::commandSwitchToUpView() 1809 { 1810 switchView(Up); 1811 return true; 1812 } 1813 1814 bool NormalViMode::commandSwitchToRightView() 1815 { 1816 switchView(Right); 1817 return true; 1818 } 1819 1820 bool NormalViMode::commandSwitchToNextView() 1821 { 1822 switchView(Next); 1823 return true; 1824 } 1825 1826 bool NormalViMode::commandSplitHoriz() 1827 { 1828 return executeKateCommand(QStringLiteral("split")); 1829 } 1830 1831 bool NormalViMode::commandSplitVert() 1832 { 1833 return executeKateCommand(QStringLiteral("vsplit")); 1834 } 1835 1836 bool NormalViMode::commandCloseView() 1837 { 1838 return executeKateCommand(QStringLiteral("close")); 1839 } 1840 1841 bool NormalViMode::commandSwitchToNextTab() 1842 { 1843 QString command = QStringLiteral("bn"); 1844 1845 if (m_iscounted) { 1846 command = command + QLatin1Char(' ') + QString::number(getCount()); 1847 } 1848 1849 return executeKateCommand(command); 1850 } 1851 1852 bool NormalViMode::commandSwitchToPrevTab() 1853 { 1854 QString command = QStringLiteral("bp"); 1855 1856 if (m_iscounted) { 1857 command = command + QLatin1Char(' ') + QString::number(getCount()); 1858 } 1859 1860 return executeKateCommand(command); 1861 } 1862 1863 bool NormalViMode::commandFormatLine() 1864 { 1865 KTextEditor::Cursor c(m_view->cursorPosition()); 1866 1867 reformatLines(c.line(), c.line() + getCount() - 1); 1868 1869 return true; 1870 } 1871 1872 bool NormalViMode::commandFormatLines() 1873 { 1874 reformatLines(m_commandRange.startLine, m_commandRange.endLine); 1875 return true; 1876 } 1877 1878 bool NormalViMode::commandCollapseToplevelNodes() 1879 { 1880 #if 0 1881 //FIXME FOLDING 1882 doc()->foldingTree()->collapseToplevelNodes(); 1883 #endif 1884 return true; 1885 } 1886 1887 bool NormalViMode::commandStartRecordingMacro() 1888 { 1889 const QChar reg = m_keys[m_keys.size() - 1]; 1890 m_viInputModeManager->macroRecorder()->start(reg); 1891 return true; 1892 } 1893 1894 bool NormalViMode::commandReplayMacro() 1895 { 1896 // "@<registername>" will have been added to the log; it needs to be cleared 1897 // *before* we replay the macro keypresses, else it can cause an infinite loop 1898 // if the macro contains a "." 1899 m_viInputModeManager->clearCurrentChangeLog(); 1900 const QChar reg = m_keys[m_keys.size() - 1]; 1901 const unsigned int count = getCount(); 1902 resetParser(); 1903 doc()->editStart(); 1904 for (unsigned int i = 0; i < count; i++) { 1905 m_viInputModeManager->macroRecorder()->replay(reg); 1906 } 1907 doc()->editEnd(); 1908 return true; 1909 } 1910 1911 bool NormalViMode::commandCloseNocheck() 1912 { 1913 return executeKateCommand(QStringLiteral("q!")); 1914 } 1915 1916 bool NormalViMode::commandCloseWrite() 1917 { 1918 return executeKateCommand(QStringLiteral("wq")); 1919 } 1920 1921 bool NormalViMode::commandCollapseLocal() 1922 { 1923 #if 0 1924 //FIXME FOLDING 1925 KTextEditor::Cursor c(m_view->cursorPosition()); 1926 doc()->foldingTree()->collapseOne(c.line(), c.column()); 1927 #endif 1928 return true; 1929 } 1930 1931 bool NormalViMode::commandExpandAll() 1932 { 1933 #if 0 1934 //FIXME FOLDING 1935 doc()->foldingTree()->expandAll(); 1936 #endif 1937 return true; 1938 } 1939 1940 bool NormalViMode::commandExpandLocal() 1941 { 1942 #if 0 1943 //FIXME FOLDING 1944 KTextEditor::Cursor c(m_view->cursorPosition()); 1945 doc()->foldingTree()->expandOne(c.line() + 1, c.column()); 1946 #endif 1947 return true; 1948 } 1949 1950 bool NormalViMode::commandToggleRegionVisibility() 1951 { 1952 #if 0 1953 //FIXME FOLDING 1954 KTextEditor::Cursor c(m_view->cursorPosition()); 1955 doc()->foldingTree()->toggleRegionVisibility(c.line()); 1956 #endif 1957 return true; 1958 } 1959 1960 //////////////////////////////////////////////////////////////////////////////// 1961 // MOTIONS 1962 //////////////////////////////////////////////////////////////////////////////// 1963 1964 Range NormalViMode::motionDown() 1965 { 1966 return goLineDown(); 1967 } 1968 1969 Range NormalViMode::motionUp() 1970 { 1971 return goLineUp(); 1972 } 1973 1974 Range NormalViMode::motionLeft() 1975 { 1976 KTextEditor::Cursor cursor(m_view->cursorPosition()); 1977 m_stickyColumn = -1; 1978 Range r(cursor, ExclusiveMotion); 1979 r.endColumn -= getCount(); 1980 1981 if (r.endColumn < 0) { 1982 r.endColumn = 0; 1983 } 1984 1985 return r; 1986 } 1987 1988 Range NormalViMode::motionRight() 1989 { 1990 KTextEditor::Cursor cursor(m_view->cursorPosition()); 1991 m_stickyColumn = -1; 1992 Range r(cursor, ExclusiveMotion); 1993 r.endColumn += getCount(); 1994 1995 // make sure end position isn't > line length 1996 if (r.endColumn > doc()->lineLength(r.endLine)) { 1997 r.endColumn = doc()->lineLength(r.endLine); 1998 } 1999 2000 return r; 2001 } 2002 2003 Range NormalViMode::motionPageDown() 2004 { 2005 KTextEditor::Cursor c(m_view->cursorPosition()); 2006 Range r(c, InclusiveMotion); 2007 r.endLine += linesDisplayed(); 2008 2009 if (r.endLine >= doc()->lines()) { 2010 r.endLine = doc()->lines() - 1; 2011 } 2012 return r; 2013 } 2014 2015 Range NormalViMode::motionPageUp() 2016 { 2017 KTextEditor::Cursor c(m_view->cursorPosition()); 2018 Range r(c, InclusiveMotion); 2019 r.endLine -= linesDisplayed(); 2020 2021 if (r.endLine < 0) { 2022 r.endLine = 0; 2023 } 2024 return r; 2025 } 2026 2027 Range NormalViMode::motionHalfPageDown() 2028 { 2029 if (commandScrollHalfPageDown()) { 2030 KTextEditor::Cursor c = m_view->cursorPosition(); 2031 m_commandRange.endLine = c.line(); 2032 m_commandRange.endColumn = c.column(); 2033 return m_commandRange; 2034 } 2035 return Range::invalid(); 2036 } 2037 2038 Range NormalViMode::motionHalfPageUp() 2039 { 2040 if (commandScrollHalfPageUp()) { 2041 KTextEditor::Cursor c = m_view->cursorPosition(); 2042 m_commandRange.endLine = c.line(); 2043 m_commandRange.endColumn = c.column(); 2044 return m_commandRange; 2045 } 2046 return Range::invalid(); 2047 } 2048 2049 Range NormalViMode::motionDownToFirstNonBlank() 2050 { 2051 Range r = goLineDown(); 2052 r.endColumn = getFirstNonBlank(r.endLine); 2053 return r; 2054 } 2055 2056 Range NormalViMode::motionUpToFirstNonBlank() 2057 { 2058 Range r = goLineUp(); 2059 r.endColumn = getFirstNonBlank(r.endLine); 2060 return r; 2061 } 2062 2063 Range NormalViMode::motionWordForward() 2064 { 2065 KTextEditor::Cursor c(m_view->cursorPosition()); 2066 Range r(c, ExclusiveMotion); 2067 2068 m_stickyColumn = -1; 2069 2070 // Special case: If we're already on the very last character in the document, the motion should be 2071 // inclusive so the last character gets included 2072 if (c.line() == doc()->lines() - 1 && c.column() == doc()->lineLength(c.line()) - 1) { 2073 r.motionType = InclusiveMotion; 2074 } else { 2075 for (int i = 0; i < getCount(); i++) { 2076 c = findNextWordStart(c.line(), c.column()); 2077 2078 // stop when at the last char in the document 2079 if (!c.isValid()) { 2080 c = doc()->documentEnd(); 2081 // if we still haven't "used up the count", make the motion inclusive, so that the last char 2082 // is included 2083 if (i < getCount()) { 2084 r.motionType = InclusiveMotion; 2085 } 2086 break; 2087 } 2088 } 2089 } 2090 2091 r.endColumn = c.column(); 2092 r.endLine = c.line(); 2093 2094 return r; 2095 } 2096 2097 Range NormalViMode::motionWordBackward() 2098 { 2099 KTextEditor::Cursor c(m_view->cursorPosition()); 2100 Range r(c, ExclusiveMotion); 2101 2102 m_stickyColumn = -1; 2103 2104 for (int i = 0; i < getCount(); i++) { 2105 c = findPrevWordStart(c.line(), c.column()); 2106 2107 if (!c.isValid()) { 2108 c = KTextEditor::Cursor(0, 0); 2109 break; 2110 } 2111 } 2112 2113 r.endColumn = c.column(); 2114 r.endLine = c.line(); 2115 2116 return r; 2117 } 2118 2119 Range NormalViMode::motionWORDForward() 2120 { 2121 KTextEditor::Cursor c(m_view->cursorPosition()); 2122 Range r(c, ExclusiveMotion); 2123 2124 m_stickyColumn = -1; 2125 2126 for (int i = 0; i < getCount(); i++) { 2127 c = findNextWORDStart(c.line(), c.column()); 2128 2129 // stop when at the last char in the document 2130 if (c.line() == doc()->lines() - 1 && c.column() == doc()->lineLength(c.line()) - 1) { 2131 break; 2132 } 2133 } 2134 2135 r.endColumn = c.column(); 2136 r.endLine = c.line(); 2137 2138 return r; 2139 } 2140 2141 Range NormalViMode::motionWORDBackward() 2142 { 2143 KTextEditor::Cursor c(m_view->cursorPosition()); 2144 Range r(c, ExclusiveMotion); 2145 2146 m_stickyColumn = -1; 2147 2148 for (int i = 0; i < getCount(); i++) { 2149 c = findPrevWORDStart(c.line(), c.column()); 2150 2151 if (!c.isValid()) { 2152 c = KTextEditor::Cursor(0, 0); 2153 } 2154 } 2155 2156 r.endColumn = c.column(); 2157 r.endLine = c.line(); 2158 2159 return r; 2160 } 2161 2162 Range NormalViMode::motionToEndOfWord() 2163 { 2164 KTextEditor::Cursor c(m_view->cursorPosition()); 2165 Range r(c, InclusiveMotion); 2166 2167 m_stickyColumn = -1; 2168 2169 for (int i = 0; i < getCount(); i++) { 2170 c = findWordEnd(c.line(), c.column()); 2171 } 2172 2173 if (!c.isValid()) { 2174 c = doc()->documentEnd(); 2175 } 2176 2177 r.endColumn = c.column(); 2178 r.endLine = c.line(); 2179 2180 return r; 2181 } 2182 2183 Range NormalViMode::motionToEndOfWORD() 2184 { 2185 KTextEditor::Cursor c(m_view->cursorPosition()); 2186 Range r(c, InclusiveMotion); 2187 2188 m_stickyColumn = -1; 2189 2190 for (int i = 0; i < getCount(); i++) { 2191 c = findWORDEnd(c.line(), c.column()); 2192 } 2193 2194 if (!c.isValid()) { 2195 c = doc()->documentEnd(); 2196 } 2197 2198 r.endColumn = c.column(); 2199 r.endLine = c.line(); 2200 2201 return r; 2202 } 2203 2204 Range NormalViMode::motionToEndOfPrevWord() 2205 { 2206 KTextEditor::Cursor c(m_view->cursorPosition()); 2207 Range r(c, InclusiveMotion); 2208 2209 m_stickyColumn = -1; 2210 2211 for (int i = 0; i < getCount(); i++) { 2212 c = findPrevWordEnd(c.line(), c.column()); 2213 2214 if (c.isValid()) { 2215 r.endColumn = c.column(); 2216 r.endLine = c.line(); 2217 } else { 2218 r.endColumn = 0; 2219 r.endLine = 0; 2220 break; 2221 } 2222 } 2223 2224 return r; 2225 } 2226 2227 Range NormalViMode::motionToEndOfPrevWORD() 2228 { 2229 KTextEditor::Cursor c(m_view->cursorPosition()); 2230 Range r(c, InclusiveMotion); 2231 2232 m_stickyColumn = -1; 2233 2234 for (int i = 0; i < getCount(); i++) { 2235 c = findPrevWORDEnd(c.line(), c.column()); 2236 2237 if (c.isValid()) { 2238 r.endColumn = c.column(); 2239 r.endLine = c.line(); 2240 } else { 2241 r.endColumn = 0; 2242 r.endLine = 0; 2243 break; 2244 } 2245 } 2246 2247 return r; 2248 } 2249 2250 void NormalViMode::stickStickyColumnToEOL() 2251 { 2252 if (m_keys.size() == 1) { 2253 m_stickyColumn = KateVi::EOL; 2254 } 2255 } 2256 2257 Range NormalViMode::motionToEOL() 2258 { 2259 KTextEditor::Cursor c(m_view->cursorPosition()); 2260 2261 stickStickyColumnToEOL(); 2262 2263 unsigned int line = c.line() + (getCount() - 1); 2264 Range r(line, doc()->lineLength(line) - 1, InclusiveMotion); 2265 2266 return r; 2267 } 2268 Range NormalViMode::motionToLastNonBlank() 2269 { 2270 KTextEditor::Cursor c(m_view->cursorPosition()); 2271 2272 stickStickyColumnToEOL(); 2273 2274 unsigned int line = c.line() + (getCount() - 1); 2275 2276 const auto text_line = doc()->plainKateTextLine(line); 2277 Range r(line, text_line.previousNonSpaceChar(text_line.length()), InclusiveMotion); 2278 return r; 2279 } 2280 2281 Range NormalViMode::motionToColumn0() 2282 { 2283 m_stickyColumn = -1; 2284 KTextEditor::Cursor cursor(m_view->cursorPosition()); 2285 Range r(cursor.line(), 0, ExclusiveMotion); 2286 2287 return r; 2288 } 2289 2290 Range NormalViMode::motionToFirstCharacterOfLine() 2291 { 2292 m_stickyColumn = -1; 2293 2294 KTextEditor::Cursor cursor(m_view->cursorPosition()); 2295 int c = getFirstNonBlank(); 2296 2297 Range r(cursor.line(), c, ExclusiveMotion); 2298 2299 return r; 2300 } 2301 2302 Range NormalViMode::motionFindChar() 2303 { 2304 m_lastTFcommand = m_keys; 2305 KTextEditor::Cursor cursor(m_view->cursorPosition()); 2306 QString line = getLine(); 2307 2308 m_stickyColumn = -1; 2309 2310 int matchColumn = cursor.column(); 2311 2312 for (int i = 0; i < getCount(); i++) { 2313 matchColumn = line.indexOf(QStringView(m_keys).right(1), matchColumn + 1); 2314 if (matchColumn == -1) { 2315 break; 2316 } 2317 } 2318 2319 Range r; 2320 2321 if (matchColumn != -1) { 2322 r.endColumn = matchColumn; 2323 r.endLine = cursor.line(); 2324 } else { 2325 return Range::invalid(); 2326 } 2327 2328 return r; 2329 } 2330 2331 Range NormalViMode::motionFindCharBackward() 2332 { 2333 m_lastTFcommand = m_keys; 2334 KTextEditor::Cursor cursor(m_view->cursorPosition()); 2335 QString line = getLine(); 2336 2337 m_stickyColumn = -1; 2338 2339 int matchColumn = -1; 2340 2341 int hits = 0; 2342 int i = cursor.column() - 1; 2343 2344 while (hits != getCount() && i >= 0) { 2345 if (line.at(i) == m_keys.at(m_keys.size() - 1)) { 2346 hits++; 2347 } 2348 2349 if (hits == getCount()) { 2350 matchColumn = i; 2351 } 2352 2353 i--; 2354 } 2355 2356 Range r(cursor, ExclusiveMotion); 2357 2358 if (matchColumn != -1) { 2359 r.endColumn = matchColumn; 2360 r.endLine = cursor.line(); 2361 } else { 2362 return Range::invalid(); 2363 } 2364 2365 return r; 2366 } 2367 2368 Range NormalViMode::motionToChar() 2369 { 2370 m_lastTFcommand = m_keys; 2371 KTextEditor::Cursor cursor(m_view->cursorPosition()); 2372 QString line = getLine(); 2373 2374 m_stickyColumn = -1; 2375 Range r; 2376 r.endColumn = -1; 2377 r.endLine = -1; 2378 2379 int matchColumn = cursor.column() + (m_isRepeatedTFcommand ? 2 : 1); 2380 2381 for (int i = 0; i < getCount(); i++) { 2382 const int lastColumn = matchColumn; 2383 matchColumn = line.indexOf(m_keys.right(1), matchColumn + ((i > 0) ? 1 : 0)); 2384 if (matchColumn == -1) { 2385 if (m_isRepeatedTFcommand) { 2386 matchColumn = lastColumn; 2387 } else { 2388 return Range::invalid(); 2389 } 2390 break; 2391 } 2392 } 2393 2394 r.endColumn = matchColumn - 1; 2395 r.endLine = cursor.line(); 2396 2397 m_isRepeatedTFcommand = false; 2398 return r; 2399 } 2400 2401 Range NormalViMode::motionToCharBackward() 2402 { 2403 m_lastTFcommand = m_keys; 2404 KTextEditor::Cursor cursor(m_view->cursorPosition()); 2405 QString line = getLine(); 2406 2407 const int originalColumn = cursor.column(); 2408 m_stickyColumn = -1; 2409 2410 int matchColumn = originalColumn - 1; 2411 2412 int hits = 0; 2413 int i = cursor.column() - (m_isRepeatedTFcommand ? 2 : 1); 2414 2415 Range r(cursor, ExclusiveMotion); 2416 2417 while (hits != getCount() && i >= 0) { 2418 if (line.at(i) == m_keys.at(m_keys.size() - 1)) { 2419 hits++; 2420 } 2421 2422 if (hits == getCount()) { 2423 matchColumn = i; 2424 } 2425 2426 i--; 2427 } 2428 2429 if (hits == getCount()) { 2430 r.endColumn = matchColumn + 1; 2431 r.endLine = cursor.line(); 2432 } else { 2433 r.valid = false; 2434 } 2435 2436 m_isRepeatedTFcommand = false; 2437 2438 return r; 2439 } 2440 2441 Range NormalViMode::motionRepeatlastTF() 2442 { 2443 if (!m_lastTFcommand.isEmpty()) { 2444 m_isRepeatedTFcommand = true; 2445 m_keys = m_lastTFcommand; 2446 if (m_keys.at(0) == QLatin1Char('f')) { 2447 return motionFindChar(); 2448 } else if (m_keys.at(0) == QLatin1Char('F')) { 2449 return motionFindCharBackward(); 2450 } else if (m_keys.at(0) == QLatin1Char('t')) { 2451 return motionToChar(); 2452 } else if (m_keys.at(0) == QLatin1Char('T')) { 2453 return motionToCharBackward(); 2454 } 2455 } 2456 2457 // there was no previous t/f command 2458 return Range::invalid(); 2459 } 2460 2461 Range NormalViMode::motionRepeatlastTFBackward() 2462 { 2463 if (!m_lastTFcommand.isEmpty()) { 2464 m_isRepeatedTFcommand = true; 2465 m_keys = m_lastTFcommand; 2466 if (m_keys.at(0) == QLatin1Char('f')) { 2467 return motionFindCharBackward(); 2468 } else if (m_keys.at(0) == QLatin1Char('F')) { 2469 return motionFindChar(); 2470 } else if (m_keys.at(0) == QLatin1Char('t')) { 2471 return motionToCharBackward(); 2472 } else if (m_keys.at(0) == QLatin1Char('T')) { 2473 return motionToChar(); 2474 } 2475 } 2476 2477 // there was no previous t/f command 2478 return Range::invalid(); 2479 } 2480 2481 Range NormalViMode::motionToLineFirst() 2482 { 2483 Range r(getCount() - 1, 0, InclusiveMotion); 2484 m_stickyColumn = -1; 2485 2486 if (r.endLine > doc()->lines() - 1) { 2487 r.endLine = doc()->lines() - 1; 2488 } 2489 r.jump = true; 2490 2491 return r; 2492 } 2493 2494 Range NormalViMode::motionToLineLast() 2495 { 2496 Range r(doc()->lines() - 1, 0, InclusiveMotion); 2497 m_stickyColumn = -1; 2498 2499 // don't use getCount() here, no count and a count of 1 is different here... 2500 if (m_count != 0) { 2501 r.endLine = m_count - 1; 2502 } 2503 2504 if (r.endLine > doc()->lines() - 1) { 2505 r.endLine = doc()->lines() - 1; 2506 } 2507 r.jump = true; 2508 2509 return r; 2510 } 2511 2512 Range NormalViMode::motionToScreenColumn() 2513 { 2514 m_stickyColumn = -1; 2515 2516 KTextEditor::Cursor c(m_view->cursorPosition()); 2517 2518 int column = getCount() - 1; 2519 2520 if (doc()->lineLength(c.line()) - 1 < (int)getCount() - 1) { 2521 column = doc()->lineLength(c.line()) - 1; 2522 } 2523 2524 return Range(c.line(), column, ExclusiveMotion); 2525 } 2526 2527 Range NormalViMode::motionToMark() 2528 { 2529 Range r; 2530 2531 m_stickyColumn = -1; 2532 2533 QChar reg = m_keys.at(m_keys.size() - 1); 2534 2535 KTextEditor::Cursor c = m_viInputModeManager->marks()->getMarkPosition(reg); 2536 if (c.isValid()) { 2537 r.endLine = c.line(); 2538 r.endColumn = c.column(); 2539 } else { 2540 error(i18n("Mark not set: %1", m_keys.right(1))); 2541 r.valid = false; 2542 } 2543 2544 r.jump = true; 2545 2546 return r; 2547 } 2548 2549 Range NormalViMode::motionToMarkLine() 2550 { 2551 Range r = motionToMark(); 2552 r.endColumn = getFirstNonBlank(r.endLine); 2553 r.jump = true; 2554 m_stickyColumn = -1; 2555 return r; 2556 } 2557 2558 Range NormalViMode::motionToMatchingItem() 2559 { 2560 Range r; 2561 int lines = doc()->lines(); 2562 2563 // If counted, then it's not a motion to matching item anymore, 2564 // but a motion to the N'th percentage of the document 2565 if (isCounted()) { 2566 int count = getCount(); 2567 if (count > 100) { 2568 return r; 2569 } 2570 r.endLine = qRound(lines * count / 100.0) - 1; 2571 r.endColumn = 0; 2572 return r; 2573 } 2574 2575 KTextEditor::Cursor c(m_view->cursorPosition()); 2576 2577 QString l = getLine(); 2578 int n1 = l.indexOf(m_matchItemRegex, c.column()); 2579 2580 m_stickyColumn = -1; 2581 2582 if (n1 < 0) { 2583 return Range::invalid(); 2584 } 2585 2586 const auto bracketChar = l.at(n1); 2587 // use Kate's built-in matching bracket finder for brackets 2588 if (bracketChar == QLatin1Char('(') || bracketChar == QLatin1Char(')') || bracketChar == QLatin1Char('{') || bracketChar == QLatin1Char('}') 2589 || bracketChar == QLatin1Char('[') || bracketChar == QLatin1Char(']')) { 2590 // findMatchingBracket requires us to move the cursor to the 2591 // first bracket, but we don't want the cursor to really move 2592 // in case this is e.g. a yank, so restore it to its original 2593 // position afterwards. 2594 c.setColumn(n1); 2595 const KTextEditor::Cursor oldCursorPos = m_view->cursorPosition(); 2596 updateCursor(c); 2597 2598 // find the matching one 2599 c = m_viewInternal->findMatchingBracket(); 2600 if (c > m_view->cursorPosition()) { 2601 c.setColumn(c.column() - 1); 2602 } 2603 m_view->setCursorPosition(oldCursorPos); 2604 } else { 2605 // text item we want to find a matching item for 2606 static const QRegularExpression boundaryRegex(QStringLiteral("\\b|\\s|$")); 2607 const int n2 = l.indexOf(boundaryRegex, n1); 2608 QString item = l.mid(n1, n2 - n1); 2609 QString matchingItem = m_matchingItems[item]; 2610 2611 int toFind = 1; 2612 int line = c.line(); 2613 int column = n2 - item.length(); 2614 bool reverse = false; 2615 2616 if (matchingItem.startsWith(QLatin1Char('-'))) { 2617 matchingItem.remove(0, 1); // remove the '-' 2618 reverse = true; 2619 } 2620 2621 // make sure we don't hit the text item we started the search from 2622 if (column == 0 && reverse) { 2623 column -= item.length(); 2624 } 2625 2626 int itemIdx; 2627 int matchItemIdx; 2628 2629 while (toFind > 0) { 2630 if (reverse) { 2631 itemIdx = l.lastIndexOf(item, column - 1); 2632 matchItemIdx = l.lastIndexOf(matchingItem, column - 1); 2633 2634 if (itemIdx != -1 && (matchItemIdx == -1 || itemIdx > matchItemIdx)) { 2635 ++toFind; 2636 } 2637 } else { 2638 itemIdx = l.indexOf(item, column); 2639 matchItemIdx = l.indexOf(matchingItem, column); 2640 2641 if (itemIdx != -1 && (matchItemIdx == -1 || itemIdx < matchItemIdx)) { 2642 ++toFind; 2643 } 2644 } 2645 2646 if (matchItemIdx != -1 || itemIdx != -1) { 2647 if (!reverse) { 2648 column = qMin((unsigned int)itemIdx, (unsigned int)matchItemIdx); 2649 } else { 2650 column = qMax(itemIdx, matchItemIdx); 2651 } 2652 } 2653 2654 if (matchItemIdx != -1) { // match on current line 2655 if (matchItemIdx == column) { 2656 --toFind; 2657 c.setLine(line); 2658 c.setColumn(column); 2659 } 2660 } else { // no match, advance one line if possible 2661 (reverse) ? --line : ++line; 2662 column = 0; 2663 2664 if ((!reverse && line >= lines) || (reverse && line < 0)) { 2665 r.valid = false; 2666 break; 2667 } else { 2668 l = getLine(line); 2669 } 2670 } 2671 } 2672 } 2673 2674 r.endLine = c.line(); 2675 r.endColumn = c.column(); 2676 r.jump = true; 2677 2678 return r; 2679 } 2680 2681 Range NormalViMode::motionToNextBraceBlockStart() 2682 { 2683 Range r; 2684 2685 m_stickyColumn = -1; 2686 2687 int line = findLineStartingWitchChar(QLatin1Char('{'), getCount()); 2688 2689 if (line == -1) { 2690 return Range::invalid(); 2691 } 2692 2693 r.endLine = line; 2694 r.endColumn = 0; 2695 r.jump = true; 2696 2697 if (motionWillBeUsedWithCommand()) { 2698 // Delete from cursor (inclusive) to the '{' (exclusive). 2699 // If we are on the first column, then delete the entire current line. 2700 r.motionType = ExclusiveMotion; 2701 if (m_view->cursorPosition().column() != 0) { 2702 r.endLine--; 2703 r.endColumn = doc()->lineLength(r.endLine); 2704 } 2705 } 2706 2707 return r; 2708 } 2709 2710 Range NormalViMode::motionToPreviousBraceBlockStart() 2711 { 2712 Range r; 2713 2714 m_stickyColumn = -1; 2715 2716 int line = findLineStartingWitchChar(QLatin1Char('{'), getCount(), false); 2717 2718 if (line == -1) { 2719 return Range::invalid(); 2720 } 2721 2722 r.endLine = line; 2723 r.endColumn = 0; 2724 r.jump = true; 2725 2726 if (motionWillBeUsedWithCommand()) { 2727 // With a command, do not include the { or the cursor position. 2728 r.motionType = ExclusiveMotion; 2729 } 2730 2731 return r; 2732 } 2733 2734 Range NormalViMode::motionToNextBraceBlockEnd() 2735 { 2736 Range r; 2737 2738 m_stickyColumn = -1; 2739 2740 int line = findLineStartingWitchChar(QLatin1Char('}'), getCount()); 2741 2742 if (line == -1) { 2743 return Range::invalid(); 2744 } 2745 2746 r.endLine = line; 2747 r.endColumn = 0; 2748 r.jump = true; 2749 2750 if (motionWillBeUsedWithCommand()) { 2751 // Delete from cursor (inclusive) to the '}' (exclusive). 2752 // If we are on the first column, then delete the entire current line. 2753 r.motionType = ExclusiveMotion; 2754 if (m_view->cursorPosition().column() != 0) { 2755 r.endLine--; 2756 r.endColumn = doc()->lineLength(r.endLine); 2757 } 2758 } 2759 2760 return r; 2761 } 2762 2763 Range NormalViMode::motionToPreviousBraceBlockEnd() 2764 { 2765 Range r; 2766 2767 m_stickyColumn = -1; 2768 2769 int line = findLineStartingWitchChar(QLatin1Char('}'), getCount(), false); 2770 2771 if (line == -1) { 2772 return Range::invalid(); 2773 } 2774 2775 r.endLine = line; 2776 r.endColumn = 0; 2777 r.jump = true; 2778 2779 if (motionWillBeUsedWithCommand()) { 2780 r.motionType = ExclusiveMotion; 2781 } 2782 2783 return r; 2784 } 2785 2786 Range NormalViMode::motionToNextOccurrence() 2787 { 2788 const QString word = getWordUnderCursor(); 2789 Searcher *searcher = m_viInputModeManager->searcher(); 2790 const Range match = searcher->findWordForMotion(word, false, getWordRangeUnderCursor().start(), getCount()); 2791 if (searcher->lastSearchWrapped()) { 2792 m_view->showSearchWrappedHint(/*isReverseSearch*/ false); 2793 } 2794 2795 return Range(match.startLine, match.startColumn, ExclusiveMotion); 2796 } 2797 2798 Range NormalViMode::motionToPrevOccurrence() 2799 { 2800 const QString word = getWordUnderCursor(); 2801 Searcher *searcher = m_viInputModeManager->searcher(); 2802 const Range match = searcher->findWordForMotion(word, true, getWordRangeUnderCursor().start(), getCount()); 2803 if (searcher->lastSearchWrapped()) { 2804 m_view->showSearchWrappedHint(/*isReverseSearch*/ true); 2805 } 2806 2807 return Range(match.startLine, match.startColumn, ExclusiveMotion); 2808 } 2809 2810 Range NormalViMode::motionToFirstLineOfWindow() 2811 { 2812 int lines_to_go; 2813 if (linesDisplayed() <= (unsigned int)m_viewInternal->endLine()) { 2814 lines_to_go = m_viewInternal->endLine() - linesDisplayed() - m_view->cursorPosition().line() + 1; 2815 } else { 2816 lines_to_go = -m_view->cursorPosition().line(); 2817 } 2818 2819 Range r = goLineUpDown(lines_to_go); 2820 r.endColumn = getFirstNonBlank(r.endLine); 2821 return r; 2822 } 2823 2824 Range NormalViMode::motionToMiddleLineOfWindow() 2825 { 2826 int lines_to_go; 2827 if (linesDisplayed() <= (unsigned int)m_viewInternal->endLine()) { 2828 lines_to_go = m_viewInternal->endLine() - linesDisplayed() / 2 - m_view->cursorPosition().line(); 2829 } else { 2830 lines_to_go = m_viewInternal->endLine() / 2 - m_view->cursorPosition().line(); 2831 } 2832 2833 Range r = goLineUpDown(lines_to_go); 2834 r.endColumn = getFirstNonBlank(r.endLine); 2835 return r; 2836 } 2837 2838 Range NormalViMode::motionToLastLineOfWindow() 2839 { 2840 int lines_to_go; 2841 if (linesDisplayed() <= (unsigned int)m_viewInternal->endLine()) { 2842 lines_to_go = m_viewInternal->endLine() - m_view->cursorPosition().line(); 2843 } else { 2844 lines_to_go = m_viewInternal->endLine() - m_view->cursorPosition().line(); 2845 } 2846 2847 Range r = goLineUpDown(lines_to_go); 2848 r.endColumn = getFirstNonBlank(r.endLine); 2849 return r; 2850 } 2851 2852 Range NormalViMode::motionToNextVisualLine() 2853 { 2854 return goVisualLineUpDown(getCount()); 2855 } 2856 2857 Range NormalViMode::motionToPrevVisualLine() 2858 { 2859 return goVisualLineUpDown(-getCount()); 2860 } 2861 2862 Range NormalViMode::motionToPreviousSentence() 2863 { 2864 KTextEditor::Cursor c = findSentenceStart(); 2865 int linenum = c.line(); 2866 int column = c.column(); 2867 const bool skipSpaces = doc()->line(linenum).isEmpty(); 2868 2869 if (skipSpaces) { 2870 linenum--; 2871 if (linenum >= 0) { 2872 column = doc()->line(linenum).size() - 1; 2873 } 2874 } 2875 2876 for (int i = linenum; i >= 0; i--) { 2877 const QString &line = doc()->line(i); 2878 2879 if (line.isEmpty() && !skipSpaces) { 2880 return Range(i, 0, InclusiveMotion); 2881 } 2882 2883 if (column < 0 && !line.isEmpty()) { 2884 column = line.size() - 1; 2885 } 2886 2887 for (int j = column; j >= 0; j--) { 2888 if (skipSpaces || QStringLiteral(".?!").indexOf(line.at(j)) != -1) { 2889 c.setLine(i); 2890 c.setColumn(j); 2891 updateCursor(c); 2892 c = findSentenceStart(); 2893 return Range(c, InclusiveMotion); 2894 } 2895 } 2896 column = line.size() - 1; 2897 } 2898 return Range(0, 0, InclusiveMotion); 2899 } 2900 2901 Range NormalViMode::motionToNextSentence() 2902 { 2903 KTextEditor::Cursor c = findSentenceEnd(); 2904 int linenum = c.line(); 2905 int column = c.column() + 1; 2906 const bool skipSpaces = doc()->line(linenum).isEmpty(); 2907 2908 for (int i = linenum; i < doc()->lines(); i++) { 2909 const QString &line = doc()->line(i); 2910 2911 if (line.isEmpty() && !skipSpaces) { 2912 return Range(i, 0, InclusiveMotion); 2913 } 2914 2915 for (int j = column; j < line.size(); j++) { 2916 if (!line.at(j).isSpace()) { 2917 return Range(i, j, InclusiveMotion); 2918 } 2919 } 2920 column = 0; 2921 } 2922 2923 c = doc()->documentEnd(); 2924 return Range(c, InclusiveMotion); 2925 } 2926 2927 Range NormalViMode::motionToBeforeParagraph() 2928 { 2929 KTextEditor::Cursor c(m_view->cursorPosition()); 2930 2931 int line = c.line(); 2932 2933 m_stickyColumn = -1; 2934 2935 for (int i = 0; i < getCount(); i++) { 2936 // advance at least one line, but if there are consecutive blank lines 2937 // skip them all 2938 do { 2939 line--; 2940 } while (line >= 0 && getLine(line + 1).length() == 0); 2941 while (line > 0 && getLine(line).length() != 0) { 2942 line--; 2943 } 2944 } 2945 2946 if (line < 0) { 2947 line = 0; 2948 } 2949 2950 Range r(line, 0, InclusiveMotion); 2951 2952 return r; 2953 } 2954 2955 Range NormalViMode::motionToAfterParagraph() 2956 { 2957 KTextEditor::Cursor c(m_view->cursorPosition()); 2958 2959 int line = c.line(); 2960 2961 m_stickyColumn = -1; 2962 2963 for (int i = 0; i < getCount(); i++) { 2964 // advance at least one line, but if there are consecutive blank lines 2965 // skip them all 2966 do { 2967 line++; 2968 } while (line <= doc()->lines() - 1 && getLine(line - 1).length() == 0); 2969 while (line < doc()->lines() - 1 && getLine(line).length() != 0) { 2970 line++; 2971 } 2972 } 2973 2974 if (line >= doc()->lines()) { 2975 line = doc()->lines() - 1; 2976 } 2977 2978 // if we ended up on the last line, the cursor should be placed on the last column 2979 int column = (line == doc()->lines() - 1) ? qMax(getLine(line).length() - 1, 0) : 0; 2980 2981 return Range(line, column, InclusiveMotion); 2982 } 2983 2984 Range NormalViMode::motionToIncrementalSearchMatch() 2985 { 2986 return Range(m_positionWhenIncrementalSearchBegan.line(), 2987 m_positionWhenIncrementalSearchBegan.column(), 2988 m_view->cursorPosition().line(), 2989 m_view->cursorPosition().column(), 2990 ExclusiveMotion); 2991 } 2992 2993 //////////////////////////////////////////////////////////////////////////////// 2994 // TEXT OBJECTS 2995 //////////////////////////////////////////////////////////////////////////////// 2996 2997 Range NormalViMode::textObjectAWord() 2998 { 2999 KTextEditor::Cursor c(m_view->cursorPosition()); 3000 3001 KTextEditor::Cursor c1 = c; 3002 3003 bool startedOnSpace = false; 3004 if (doc()->characterAt(c).isSpace()) { 3005 startedOnSpace = true; 3006 } else { 3007 c1 = findPrevWordStart(c.line(), c.column() + 1, true); 3008 if (!c1.isValid()) { 3009 c1 = KTextEditor::Cursor(0, 0); 3010 } 3011 } 3012 KTextEditor::Cursor c2 = KTextEditor::Cursor(c.line(), c.column() - 1); 3013 for (int i = 1; i <= getCount(); i++) { 3014 c2 = findWordEnd(c2.line(), c2.column()); 3015 } 3016 if (!c1.isValid() || !c2.isValid()) { 3017 return Range::invalid(); 3018 } 3019 // Adhere to some of Vim's bizarre rules of whether to swallow ensuing spaces or not. 3020 // Don't ask ;) 3021 const KTextEditor::Cursor nextWordStart = findNextWordStart(c2.line(), c2.column()); 3022 if (nextWordStart.isValid() && nextWordStart.line() == c2.line()) { 3023 if (!startedOnSpace) { 3024 c2 = KTextEditor::Cursor(nextWordStart.line(), nextWordStart.column() - 1); 3025 } 3026 } else { 3027 c2 = KTextEditor::Cursor(c2.line(), doc()->lineLength(c2.line()) - 1); 3028 } 3029 bool swallowCarriageReturnAtEndOfLine = false; 3030 if (c2.line() != c.line() && c2.column() == doc()->lineLength(c2.line()) - 1) { 3031 // Greedily descend to the next line, so as to swallow the carriage return on this line. 3032 c2 = KTextEditor::Cursor(c2.line() + 1, 0); 3033 swallowCarriageReturnAtEndOfLine = true; 3034 } 3035 const bool swallowPrecedingSpaces = 3036 (c2.column() == doc()->lineLength(c2.line()) - 1 && !doc()->characterAt(c2).isSpace()) || startedOnSpace || swallowCarriageReturnAtEndOfLine; 3037 if (swallowPrecedingSpaces) { 3038 if (c1.column() != 0) { 3039 const KTextEditor::Cursor previousNonSpace = findPrevWordEnd(c.line(), c.column()); 3040 if (previousNonSpace.isValid() && previousNonSpace.line() == c1.line()) { 3041 c1 = KTextEditor::Cursor(previousNonSpace.line(), previousNonSpace.column() + 1); 3042 } else if (startedOnSpace || swallowCarriageReturnAtEndOfLine) { 3043 c1 = KTextEditor::Cursor(c1.line(), 0); 3044 } 3045 } 3046 } 3047 3048 return Range(c1, c2, !swallowCarriageReturnAtEndOfLine ? InclusiveMotion : ExclusiveMotion); 3049 } 3050 3051 Range NormalViMode::textObjectInnerWord() 3052 { 3053 KTextEditor::Cursor c(m_view->cursorPosition()); 3054 3055 KTextEditor::Cursor c1 = findPrevWordStart(c.line(), c.column() + 1, true); 3056 if (!c1.isValid()) { 3057 c1 = KTextEditor::Cursor(0, 0); 3058 } 3059 // need to start search in column-1 because it might be a one-character word 3060 KTextEditor::Cursor c2(c.line(), c.column() - 1); 3061 3062 for (int i = 0; i < getCount(); i++) { 3063 c2 = findWordEnd(c2.line(), c2.column(), true); 3064 } 3065 3066 if (!c2.isValid()) { 3067 c2 = doc()->documentEnd(); 3068 } 3069 3070 // sanity check 3071 if (c1.line() != c2.line() || c1.column() > c2.column()) { 3072 return Range::invalid(); 3073 } 3074 return Range(c1, c2, InclusiveMotion); 3075 } 3076 3077 Range NormalViMode::textObjectAWORD() 3078 { 3079 KTextEditor::Cursor c(m_view->cursorPosition()); 3080 3081 KTextEditor::Cursor c1 = c; 3082 3083 bool startedOnSpace = false; 3084 if (doc()->characterAt(c).isSpace()) { 3085 startedOnSpace = true; 3086 } else { 3087 c1 = findPrevWORDStart(c.line(), c.column() + 1, true); 3088 if (!c1.isValid()) { 3089 c1 = KTextEditor::Cursor(0, 0); 3090 } 3091 } 3092 KTextEditor::Cursor c2 = KTextEditor::Cursor(c.line(), c.column() - 1); 3093 for (int i = 1; i <= getCount(); i++) { 3094 c2 = findWORDEnd(c2.line(), c2.column()); 3095 } 3096 if (!c1.isValid() || !c2.isValid()) { 3097 return Range::invalid(); 3098 } 3099 // Adhere to some of Vim's bizarre rules of whether to swallow ensuing spaces or not. 3100 // Don't ask ;) 3101 const KTextEditor::Cursor nextWordStart = findNextWordStart(c2.line(), c2.column()); 3102 if (nextWordStart.isValid() && nextWordStart.line() == c2.line()) { 3103 if (!startedOnSpace) { 3104 c2 = KTextEditor::Cursor(nextWordStart.line(), nextWordStart.column() - 1); 3105 } 3106 } else { 3107 c2 = KTextEditor::Cursor(c2.line(), doc()->lineLength(c2.line()) - 1); 3108 } 3109 bool swallowCarriageReturnAtEndOfLine = false; 3110 if (c2.line() != c.line() && c2.column() == doc()->lineLength(c2.line()) - 1) { 3111 // Greedily descend to the next line, so as to swallow the carriage return on this line. 3112 c2 = KTextEditor::Cursor(c2.line() + 1, 0); 3113 swallowCarriageReturnAtEndOfLine = true; 3114 } 3115 const bool swallowPrecedingSpaces = 3116 (c2.column() == doc()->lineLength(c2.line()) - 1 && !doc()->characterAt(c2).isSpace()) || startedOnSpace || swallowCarriageReturnAtEndOfLine; 3117 if (swallowPrecedingSpaces) { 3118 if (c1.column() != 0) { 3119 const KTextEditor::Cursor previousNonSpace = findPrevWORDEnd(c.line(), c.column()); 3120 if (previousNonSpace.isValid() && previousNonSpace.line() == c1.line()) { 3121 c1 = KTextEditor::Cursor(previousNonSpace.line(), previousNonSpace.column() + 1); 3122 } else if (startedOnSpace || swallowCarriageReturnAtEndOfLine) { 3123 c1 = KTextEditor::Cursor(c1.line(), 0); 3124 } 3125 } 3126 } 3127 3128 return Range(c1, c2, !swallowCarriageReturnAtEndOfLine ? InclusiveMotion : ExclusiveMotion); 3129 } 3130 3131 Range NormalViMode::textObjectInnerWORD() 3132 { 3133 KTextEditor::Cursor c(m_view->cursorPosition()); 3134 3135 KTextEditor::Cursor c1 = findPrevWORDStart(c.line(), c.column() + 1, true); 3136 if (!c1.isValid()) { 3137 c1 = KTextEditor::Cursor(0, 0); 3138 } 3139 KTextEditor::Cursor c2(c); 3140 3141 for (int i = 0; i < getCount(); i++) { 3142 c2 = findWORDEnd(c2.line(), c2.column(), true); 3143 } 3144 3145 if (!c2.isValid()) { 3146 c2 = doc()->documentEnd(); 3147 } 3148 3149 // sanity check 3150 if (c1.line() != c2.line() || c1.column() > c2.column()) { 3151 return Range::invalid(); 3152 } 3153 return Range(c1, c2, InclusiveMotion); 3154 } 3155 3156 KTextEditor::Cursor NormalViMode::findSentenceStart() 3157 { 3158 KTextEditor::Cursor c(m_view->cursorPosition()); 3159 int linenum = c.line(); 3160 int column = c.column(); 3161 int prev = column; 3162 3163 for (int i = linenum; i >= 0; i--) { 3164 const QString &line = doc()->line(i); 3165 const int lineLength = line.size(); 3166 if (i != linenum) { 3167 column = lineLength; 3168 } 3169 3170 // An empty line is the end of a paragraph. 3171 if (line.isEmpty()) { 3172 return KTextEditor::Cursor((i != linenum) ? i + 1 : i, prev); 3173 } 3174 3175 prev = column; 3176 for (int j = column; j >= 0; j--) { 3177 if (j == lineLength || line.at(j).isSpace()) { 3178 int lastSpace = j--; 3179 for (; j >= 0 && QStringLiteral("\"')]").indexOf(line.at(j)) != -1; j--) { 3180 ; 3181 } 3182 3183 if (j >= 0 && QStringLiteral(".!?").indexOf(line.at(j)) != -1) { 3184 if (lastSpace == lineLength) { 3185 // If the line ends with one of .!?, then the sentence starts from the next line. 3186 return KTextEditor::Cursor(i + 1, 0); 3187 } 3188 3189 return KTextEditor::Cursor(i, prev); 3190 } 3191 j = lastSpace; 3192 } else { 3193 prev = j; 3194 } 3195 } 3196 } 3197 3198 return KTextEditor::Cursor(0, 0); 3199 } 3200 3201 KTextEditor::Cursor NormalViMode::findSentenceEnd() 3202 { 3203 KTextEditor::Cursor c(m_view->cursorPosition()); 3204 int linenum = c.line(); 3205 int column = c.column(); 3206 int j = 0; 3207 int prev = 0; 3208 3209 for (int i = linenum; i < doc()->lines(); i++) { 3210 const QString &line = doc()->line(i); 3211 3212 // An empty line is the end of a paragraph. 3213 if (line.isEmpty()) { 3214 return KTextEditor::Cursor(linenum, j); 3215 } 3216 3217 // Iterating over the line to reach any '.', '!', '?' 3218 for (j = column; j < line.size(); j++) { 3219 if (QStringLiteral(".!?").indexOf(line.at(j)) != -1) { 3220 prev = j++; 3221 // Skip possible closing characters. 3222 for (; j < line.size() && QStringLiteral("\"')]").indexOf(line.at(j)) != -1; j++) { 3223 ; 3224 } 3225 3226 if (j >= line.size()) { 3227 return KTextEditor::Cursor(i, j - 1); 3228 } 3229 3230 // And hopefully we're done... 3231 if (line.at(j).isSpace()) { 3232 return KTextEditor::Cursor(i, j - 1); 3233 } 3234 j = prev; 3235 } 3236 } 3237 linenum = i; 3238 prev = column; 3239 column = 0; 3240 } 3241 3242 return KTextEditor::Cursor(linenum, j - 1); 3243 } 3244 3245 KTextEditor::Cursor NormalViMode::findParagraphStart() 3246 { 3247 KTextEditor::Cursor c(m_view->cursorPosition()); 3248 const bool firstBlank = doc()->line(c.line()).isEmpty(); 3249 int prev = c.line(); 3250 3251 for (int i = prev; i >= 0; i--) { 3252 if (doc()->line(i).isEmpty()) { 3253 if (i != prev) { 3254 prev = i + 1; 3255 } 3256 3257 /* Skip consecutive empty lines. */ 3258 if (firstBlank) { 3259 i--; 3260 for (; i >= 0 && doc()->line(i).isEmpty(); i--, prev--) { 3261 ; 3262 } 3263 } 3264 return KTextEditor::Cursor(prev, 0); 3265 } 3266 } 3267 return KTextEditor::Cursor(0, 0); 3268 } 3269 3270 KTextEditor::Cursor NormalViMode::findParagraphEnd() 3271 { 3272 KTextEditor::Cursor c(m_view->cursorPosition()); 3273 int prev = c.line(); 3274 int lines = doc()->lines(); 3275 const bool firstBlank = doc()->line(prev).isEmpty(); 3276 3277 for (int i = prev; i < lines; i++) { 3278 if (doc()->line(i).isEmpty()) { 3279 if (i != prev) { 3280 prev = i - 1; 3281 } 3282 3283 /* Skip consecutive empty lines. */ 3284 if (firstBlank) { 3285 i++; 3286 for (; i < lines && doc()->line(i).isEmpty(); i++, prev++) { 3287 ; 3288 } 3289 } 3290 int length = doc()->lineLength(prev); 3291 return KTextEditor::Cursor(prev, (length <= 0) ? 0 : length - 1); 3292 } 3293 } 3294 return doc()->documentEnd(); 3295 } 3296 3297 Range NormalViMode::textObjectInnerSentence() 3298 { 3299 Range r; 3300 KTextEditor::Cursor c1 = findSentenceStart(); 3301 KTextEditor::Cursor c2 = findSentenceEnd(); 3302 updateCursor(c1); 3303 3304 r.startLine = c1.line(); 3305 r.startColumn = c1.column(); 3306 r.endLine = c2.line(); 3307 r.endColumn = c2.column(); 3308 return r; 3309 } 3310 3311 Range NormalViMode::textObjectASentence() 3312 { 3313 int i; 3314 Range r = textObjectInnerSentence(); 3315 const QString &line = doc()->line(r.endLine); 3316 3317 // Skip whitespaces and tabs. 3318 for (i = r.endColumn + 1; i < line.size(); i++) { 3319 if (!line.at(i).isSpace()) { 3320 break; 3321 } 3322 } 3323 r.endColumn = i - 1; 3324 3325 // Remove preceding spaces. 3326 if (r.startColumn != 0) { 3327 if (r.endColumn == line.size() - 1 && !line.at(r.endColumn).isSpace()) { 3328 const QString &line = doc()->line(r.startLine); 3329 for (i = r.startColumn - 1; i >= 0; i--) { 3330 if (!line.at(i).isSpace()) { 3331 break; 3332 } 3333 } 3334 r.startColumn = i + 1; 3335 } 3336 } 3337 return r; 3338 } 3339 3340 Range NormalViMode::textObjectInnerParagraph() 3341 { 3342 Range r; 3343 KTextEditor::Cursor c1 = findParagraphStart(); 3344 KTextEditor::Cursor c2 = findParagraphEnd(); 3345 updateCursor(c1); 3346 3347 r.startLine = c1.line(); 3348 r.startColumn = c1.column(); 3349 r.endLine = c2.line(); 3350 r.endColumn = c2.column(); 3351 return r; 3352 } 3353 3354 Range NormalViMode::textObjectAParagraph() 3355 { 3356 Range r = textObjectInnerParagraph(); 3357 int lines = doc()->lines(); 3358 3359 if (r.endLine + 1 < lines) { 3360 // If the next line is empty, remove all subsequent empty lines. 3361 // Otherwise we'll grab the next paragraph. 3362 if (doc()->line(r.endLine + 1).isEmpty()) { 3363 for (int i = r.endLine + 1; i < lines && doc()->line(i).isEmpty(); i++) { 3364 r.endLine++; 3365 } 3366 r.endColumn = 0; 3367 } else { 3368 KTextEditor::Cursor prev = m_view->cursorPosition(); 3369 KTextEditor::Cursor c(r.endLine + 1, 0); 3370 updateCursor(c); 3371 c = findParagraphEnd(); 3372 updateCursor(prev); 3373 r.endLine = c.line(); 3374 r.endColumn = c.column(); 3375 } 3376 } else if (doc()->lineLength(r.startLine) > 0) { 3377 // We went too far, but maybe we can grab previous empty lines. 3378 for (int i = r.startLine - 1; i >= 0 && doc()->line(i).isEmpty(); i--) { 3379 r.startLine--; 3380 } 3381 r.startColumn = 0; 3382 updateCursor(KTextEditor::Cursor(r.startLine, r.startColumn)); 3383 } else { 3384 // We went too far and we're on empty lines, do nothing. 3385 return Range::invalid(); 3386 } 3387 return r; 3388 } 3389 3390 Range NormalViMode::textObjectAQuoteDouble() 3391 { 3392 return findSurroundingQuotes(QLatin1Char('"'), false); 3393 } 3394 3395 Range NormalViMode::textObjectInnerQuoteDouble() 3396 { 3397 return findSurroundingQuotes(QLatin1Char('"'), true); 3398 } 3399 3400 Range NormalViMode::textObjectAQuoteSingle() 3401 { 3402 return findSurroundingQuotes(QLatin1Char('\''), false); 3403 } 3404 3405 Range NormalViMode::textObjectInnerQuoteSingle() 3406 { 3407 return findSurroundingQuotes(QLatin1Char('\''), true); 3408 } 3409 3410 Range NormalViMode::textObjectABackQuote() 3411 { 3412 return findSurroundingQuotes(QLatin1Char('`'), false); 3413 } 3414 3415 Range NormalViMode::textObjectInnerBackQuote() 3416 { 3417 return findSurroundingQuotes(QLatin1Char('`'), true); 3418 } 3419 3420 Range NormalViMode::textObjectAParen() 3421 { 3422 return findSurroundingBrackets(QLatin1Char('('), QLatin1Char(')'), false, QLatin1Char('('), QLatin1Char(')')); 3423 } 3424 3425 Range NormalViMode::textObjectInnerParen() 3426 { 3427 return findSurroundingBrackets(QLatin1Char('('), QLatin1Char(')'), true, QLatin1Char('('), QLatin1Char(')')); 3428 } 3429 3430 Range NormalViMode::textObjectABracket() 3431 { 3432 return findSurroundingBrackets(QLatin1Char('['), QLatin1Char(']'), false, QLatin1Char('['), QLatin1Char(']')); 3433 } 3434 3435 Range NormalViMode::textObjectInnerBracket() 3436 { 3437 return findSurroundingBrackets(QLatin1Char('['), QLatin1Char(']'), true, QLatin1Char('['), QLatin1Char(']')); 3438 } 3439 3440 Range NormalViMode::textObjectACurlyBracket() 3441 { 3442 return findSurroundingBrackets(QLatin1Char('{'), QLatin1Char('}'), false, QLatin1Char('{'), QLatin1Char('}')); 3443 } 3444 3445 Range NormalViMode::textObjectInnerCurlyBracket() 3446 { 3447 const Range allBetweenCurlyBrackets = findSurroundingBrackets(QLatin1Char('{'), QLatin1Char('}'), true, QLatin1Char('{'), QLatin1Char('}')); 3448 // Emulate the behaviour of vim, which tries to leave the closing bracket on its own line 3449 // if it was originally on a line different to that of the opening bracket. 3450 Range innerCurlyBracket(allBetweenCurlyBrackets); 3451 3452 if (innerCurlyBracket.startLine != innerCurlyBracket.endLine) { 3453 const bool openingBraceIsLastCharOnLine = innerCurlyBracket.startColumn == doc()->line(innerCurlyBracket.startLine).length(); 3454 const bool stuffToDeleteIsAllOnEndLine = openingBraceIsLastCharOnLine && innerCurlyBracket.endLine == innerCurlyBracket.startLine + 1; 3455 const QString textLeadingClosingBracket = doc()->line(innerCurlyBracket.endLine).mid(0, innerCurlyBracket.endColumn + 1); 3456 const bool closingBracketHasLeadingNonWhitespace = !textLeadingClosingBracket.trimmed().isEmpty(); 3457 if (stuffToDeleteIsAllOnEndLine) { 3458 if (!closingBracketHasLeadingNonWhitespace) { 3459 // Nothing there to select - abort. 3460 return Range::invalid(); 3461 } else { 3462 // Shift the beginning of the range to the start of the line containing the closing bracket. 3463 innerCurlyBracket.startLine++; 3464 innerCurlyBracket.startColumn = 0; 3465 } 3466 } else { 3467 if (openingBraceIsLastCharOnLine && !closingBracketHasLeadingNonWhitespace) { 3468 innerCurlyBracket.startLine++; 3469 innerCurlyBracket.startColumn = 0; 3470 m_lastMotionWasLinewiseInnerBlock = true; 3471 } 3472 { 3473 // The line containing the end bracket is left alone if the end bracket is preceded by just whitespace, 3474 // else we need to delete everything (i.e. end up with "{}") 3475 if (!closingBracketHasLeadingNonWhitespace) { 3476 // Shrink the endpoint of the range so that it ends at the end of the line above, 3477 // leaving the closing bracket on its own line. 3478 innerCurlyBracket.endLine--; 3479 innerCurlyBracket.endColumn = doc()->line(innerCurlyBracket.endLine).length(); 3480 } 3481 } 3482 } 3483 } 3484 return innerCurlyBracket; 3485 } 3486 3487 Range NormalViMode::textObjectAInequalitySign() 3488 { 3489 return findSurroundingBrackets(QLatin1Char('<'), QLatin1Char('>'), false, QLatin1Char('<'), QLatin1Char('>')); 3490 } 3491 3492 Range NormalViMode::textObjectInnerInequalitySign() 3493 { 3494 return findSurroundingBrackets(QLatin1Char('<'), QLatin1Char('>'), true, QLatin1Char('<'), QLatin1Char('>')); 3495 } 3496 3497 Range NormalViMode::textObjectAComma() 3498 { 3499 return textObjectComma(false); 3500 } 3501 3502 Range NormalViMode::textObjectInnerComma() 3503 { 3504 return textObjectComma(true); 3505 } 3506 3507 QRegularExpression NormalViMode::generateMatchingItemRegex() const 3508 { 3509 QString pattern(QStringLiteral("\\[|\\]|\\{|\\}|\\(|\\)|")); 3510 3511 for (QString s : std::as_const(m_matchingItems)) { 3512 if (s.startsWith(QLatin1Char('-'))) { 3513 s.remove(0, 1); 3514 } 3515 s.replace(QLatin1Char('*'), QStringLiteral("\\*")); 3516 s.replace(QLatin1Char('+'), QStringLiteral("\\+")); 3517 s.replace(QLatin1Char('['), QStringLiteral("\\[")); 3518 s.replace(QLatin1Char(']'), QStringLiteral("\\]")); 3519 s.replace(QLatin1Char('('), QStringLiteral("\\(")); 3520 s.replace(QLatin1Char(')'), QStringLiteral("\\)")); 3521 s.replace(QLatin1Char('{'), QStringLiteral("\\{")); 3522 s.replace(QLatin1Char('}'), QStringLiteral("\\}")); 3523 3524 s.append(QLatin1Char('|')); 3525 pattern.append(s); 3526 } 3527 // remove extra "|" at the end 3528 pattern.chop(1); 3529 3530 return QRegularExpression(pattern); 3531 } 3532 3533 // returns the operation mode that should be used. this is decided by using the following heuristic: 3534 // 1. if we're in visual block mode, it should be Block 3535 // 2. if we're in visual line mode OR the range spans several lines, it should be LineWise 3536 // 3. if neither of these is true, CharWise is returned 3537 // 4. there are some motion that makes all operator charwise, if we have one of them mode will be CharWise 3538 OperationMode NormalViMode::getOperationMode() const 3539 { 3540 OperationMode m = CharWise; 3541 3542 if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualBlockMode) { 3543 m = Block; 3544 } else if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualLineMode 3545 || (m_commandRange.startLine != m_commandRange.endLine && m_viInputModeManager->getCurrentViMode() != ViMode::VisualMode)) { 3546 m = LineWise; 3547 } 3548 3549 if (m_commandWithMotion && !m_linewiseCommand) { 3550 m = CharWise; 3551 } 3552 3553 if (m_lastMotionWasLinewiseInnerBlock) { 3554 m = LineWise; 3555 } 3556 3557 return m; 3558 } 3559 3560 bool NormalViMode::paste(PasteLocation pasteLocation, bool isgPaste, bool isIndentedPaste) 3561 { 3562 KTextEditor::Cursor pasteAt(m_view->cursorPosition()); 3563 KTextEditor::Cursor cursorAfterPaste = pasteAt; 3564 QChar reg = getChosenRegister(UnnamedRegister); 3565 3566 OperationMode m = getRegisterFlag(reg); 3567 QString textToInsert = getRegisterContent(reg); 3568 const bool isTextMultiLine = textToInsert.count(QLatin1Char('\n')) > 0; 3569 3570 // In temporary normal mode, p/P act as gp/gP. 3571 isgPaste |= m_viInputModeManager->getTemporaryNormalMode(); 3572 3573 if (textToInsert.isEmpty()) { 3574 error(i18n("Nothing in register %1", reg.toLower())); 3575 return false; 3576 } 3577 3578 if (getCount() > 1) { 3579 textToInsert = textToInsert.repeated(getCount()); // FIXME: does this make sense for blocks? 3580 } 3581 3582 if (m == LineWise) { 3583 pasteAt.setColumn(0); 3584 if (isIndentedPaste) { 3585 // Note that this does indeed work if there is no non-whitespace on the current line or if 3586 // the line is empty! 3587 static const QRegularExpression nonWhitespaceRegex(QStringLiteral("[^\\s]")); 3588 const QString pasteLineString = doc()->line(pasteAt.line()); 3589 const QString leadingWhiteSpaceOnCurrentLine = pasteLineString.mid(0, pasteLineString.indexOf(nonWhitespaceRegex)); 3590 const QString leadingWhiteSpaceOnFirstPastedLine = textToInsert.mid(0, textToInsert.indexOf(nonWhitespaceRegex)); 3591 // QString has no "left trim" method, bizarrely. 3592 while (textToInsert[0].isSpace()) { 3593 textToInsert = textToInsert.mid(1); 3594 } 3595 textToInsert.prepend(leadingWhiteSpaceOnCurrentLine); 3596 // Remove the last \n, temporarily: we're going to alter the indentation of each pasted line 3597 // by doing a search and replace on '\n's, but don't want to alter this one. 3598 textToInsert.chop(1); 3599 textToInsert.replace(QLatin1Char('\n') + leadingWhiteSpaceOnFirstPastedLine, QLatin1Char('\n') + leadingWhiteSpaceOnCurrentLine); 3600 textToInsert.append(QLatin1Char('\n')); // Re-add the temporarily removed last '\n'. 3601 } 3602 if (pasteLocation == AfterCurrentPosition) { 3603 textToInsert.chop(1); // remove the last \n 3604 pasteAt.setColumn(doc()->lineLength(pasteAt.line())); // paste after the current line and ... 3605 textToInsert.prepend(QLatin1Char('\n')); // ... prepend a \n, so the text starts on a new line 3606 3607 cursorAfterPaste.setLine(cursorAfterPaste.line() + 1); 3608 } 3609 if (isgPaste) { 3610 cursorAfterPaste.setLine(cursorAfterPaste.line() + textToInsert.count(QLatin1Char('\n'))); 3611 } 3612 } else { 3613 if (pasteLocation == AfterCurrentPosition) { 3614 // Move cursor forward one before we paste. The position after the paste must also 3615 // be updated accordingly. 3616 if (getLine(pasteAt.line()).length() > 0) { 3617 pasteAt.setColumn(pasteAt.column() + 1); 3618 } 3619 cursorAfterPaste = pasteAt; 3620 } 3621 const bool leaveCursorAtStartOfPaste = isTextMultiLine && !isgPaste; 3622 if (!leaveCursorAtStartOfPaste) { 3623 cursorAfterPaste = cursorPosAtEndOfPaste(pasteAt, textToInsert); 3624 if (!isgPaste) { 3625 cursorAfterPaste.setColumn(cursorAfterPaste.column() - 1); 3626 } 3627 } 3628 } 3629 3630 doc()->editStart(); 3631 if (m_view->selection()) { 3632 pasteAt = m_view->selectionRange().start(); 3633 doc()->removeText(m_view->selectionRange()); 3634 } 3635 doc()->insertText(pasteAt, textToInsert, m == Block); 3636 doc()->editEnd(); 3637 3638 if (cursorAfterPaste.line() >= doc()->lines()) { 3639 cursorAfterPaste.setLine(doc()->lines() - 1); 3640 } 3641 updateCursor(cursorAfterPaste); 3642 3643 return true; 3644 } 3645 3646 KTextEditor::Cursor NormalViMode::cursorPosAtEndOfPaste(const KTextEditor::Cursor pasteLocation, const QString &pastedText) 3647 { 3648 KTextEditor::Cursor cAfter = pasteLocation; 3649 const int lineCount = pastedText.count(QLatin1Char('\n')) + 1; 3650 if (lineCount == 1) { 3651 cAfter.setColumn(cAfter.column() + pastedText.length()); 3652 } else { 3653 const int lastLineLength = pastedText.size() - (pastedText.lastIndexOf(QLatin1Char('\n')) + 1); 3654 cAfter.setColumn(lastLineLength); 3655 cAfter.setLine(cAfter.line() + lineCount - 1); 3656 } 3657 return cAfter; 3658 } 3659 3660 void NormalViMode::joinLines(unsigned int from, unsigned int to) const 3661 { 3662 // make sure we don't try to join lines past the document end 3663 if (to >= (unsigned int)(doc()->lines())) { 3664 to = doc()->lines() - 1; 3665 } 3666 3667 // joining one line is a no-op 3668 if (from == to) { 3669 return; 3670 } 3671 3672 doc()->joinLines(from, to); 3673 } 3674 3675 void NormalViMode::reformatLines(unsigned int from, unsigned int to) const 3676 { 3677 // BUG #340550: Do not remove empty lines when reformatting 3678 KTextEditor::DocumentPrivate *document = doc(); 3679 auto isNonEmptyLine = [](QStringView text) { 3680 for (int i = 0; i < text.length(); ++i) { 3681 if (!text.at(i).isSpace()) { 3682 return true; 3683 } 3684 } 3685 3686 return false; 3687 }; 3688 for (; from < to; ++from) { 3689 if (isNonEmptyLine(document->line(from))) { 3690 break; 3691 } 3692 } 3693 for (; to > from; --to) { 3694 if (isNonEmptyLine(document->line(to))) { 3695 break; 3696 } 3697 } 3698 3699 document->editStart(); 3700 joinLines(from, to); 3701 document->wrapText(from, to); 3702 document->editEnd(); 3703 } 3704 3705 int NormalViMode::getFirstNonBlank(int line) const 3706 { 3707 if (line < 0) { 3708 line = m_view->cursorPosition().line(); 3709 } 3710 3711 // doc()->plainKateTextLine returns NULL if the line is out of bounds. 3712 Kate::TextLine l = doc()->plainKateTextLine(line); 3713 int c = l.firstChar(); 3714 return (c < 0) ? 0 : c; 3715 } 3716 3717 // Tries to shrinks toShrink so that it fits tightly around rangeToShrinkTo. 3718 void NormalViMode::shrinkRangeAroundCursor(Range &toShrink, const Range &rangeToShrinkTo) const 3719 { 3720 if (!toShrink.valid || !rangeToShrinkTo.valid) { 3721 return; 3722 } 3723 KTextEditor::Cursor cursorPos = m_view->cursorPosition(); 3724 if (rangeToShrinkTo.startLine >= cursorPos.line()) { 3725 if (rangeToShrinkTo.startLine > cursorPos.line()) { 3726 // Does not surround cursor; aborting. 3727 return; 3728 } 3729 Q_ASSERT(rangeToShrinkTo.startLine == cursorPos.line()); 3730 if (rangeToShrinkTo.startColumn > cursorPos.column()) { 3731 // Does not surround cursor; aborting. 3732 return; 3733 } 3734 } 3735 if (rangeToShrinkTo.endLine <= cursorPos.line()) { 3736 if (rangeToShrinkTo.endLine < cursorPos.line()) { 3737 // Does not surround cursor; aborting. 3738 return; 3739 } 3740 Q_ASSERT(rangeToShrinkTo.endLine == cursorPos.line()); 3741 if (rangeToShrinkTo.endColumn < cursorPos.column()) { 3742 // Does not surround cursor; aborting. 3743 return; 3744 } 3745 } 3746 3747 if (toShrink.startLine <= rangeToShrinkTo.startLine) { 3748 if (toShrink.startLine < rangeToShrinkTo.startLine) { 3749 toShrink.startLine = rangeToShrinkTo.startLine; 3750 toShrink.startColumn = rangeToShrinkTo.startColumn; 3751 } 3752 Q_ASSERT(toShrink.startLine == rangeToShrinkTo.startLine); 3753 if (toShrink.startColumn < rangeToShrinkTo.startColumn) { 3754 toShrink.startColumn = rangeToShrinkTo.startColumn; 3755 } 3756 } 3757 if (toShrink.endLine >= rangeToShrinkTo.endLine) { 3758 if (toShrink.endLine > rangeToShrinkTo.endLine) { 3759 toShrink.endLine = rangeToShrinkTo.endLine; 3760 toShrink.endColumn = rangeToShrinkTo.endColumn; 3761 } 3762 Q_ASSERT(toShrink.endLine == rangeToShrinkTo.endLine); 3763 if (toShrink.endColumn > rangeToShrinkTo.endColumn) { 3764 toShrink.endColumn = rangeToShrinkTo.endColumn; 3765 } 3766 } 3767 } 3768 3769 Range NormalViMode::textObjectComma(bool inner) const 3770 { 3771 // Basic algorithm: look left and right of the cursor for all combinations 3772 // of enclosing commas and the various types of brackets, and pick the pair 3773 // closest to the cursor that surrounds the cursor. 3774 Range r(0, 0, m_view->doc()->lines(), m_view->doc()->line(m_view->doc()->lastLine()).length(), InclusiveMotion); 3775 3776 shrinkRangeAroundCursor(r, findSurroundingQuotes(QLatin1Char(','), inner)); 3777 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('('), QLatin1Char(')'), inner, QLatin1Char('('), QLatin1Char(')'))); 3778 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('{'), QLatin1Char('}'), inner, QLatin1Char('{'), QLatin1Char('}'))); 3779 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char(','), QLatin1Char(')'), inner, QLatin1Char('('), QLatin1Char(')'))); 3780 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char(','), QLatin1Char(']'), inner, QLatin1Char('['), QLatin1Char(']'))); 3781 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char(','), QLatin1Char('}'), inner, QLatin1Char('{'), QLatin1Char('}'))); 3782 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('('), QLatin1Char(','), inner, QLatin1Char('('), QLatin1Char(')'))); 3783 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('['), QLatin1Char(','), inner, QLatin1Char('['), QLatin1Char(']'))); 3784 shrinkRangeAroundCursor(r, findSurroundingBrackets(QLatin1Char('{'), QLatin1Char(','), inner, QLatin1Char('{'), QLatin1Char('}'))); 3785 return r; 3786 } 3787 3788 void NormalViMode::updateYankHighlightAttrib() 3789 { 3790 if (!m_highlightYankAttribute) { 3791 m_highlightYankAttribute = new KTextEditor::Attribute; 3792 } 3793 const QColor &yankedColor = m_view->rendererConfig()->savedLineColor(); 3794 m_highlightYankAttribute->setBackground(yankedColor); 3795 KTextEditor::Attribute::Ptr mouseInAttribute(new KTextEditor::Attribute()); 3796 mouseInAttribute->setFontBold(true); 3797 m_highlightYankAttribute->setDynamicAttribute(KTextEditor::Attribute::ActivateMouseIn, mouseInAttribute); 3798 m_highlightYankAttribute->dynamicAttribute(KTextEditor::Attribute::ActivateMouseIn)->setBackground(yankedColor); 3799 } 3800 3801 void NormalViMode::highlightYank(const Range &range, const OperationMode mode) 3802 { 3803 clearYankHighlight(); 3804 3805 // current MovingRange doesn't support block mode selection so split the 3806 // block range into per-line ranges 3807 if (mode == Block) { 3808 for (int i = range.startLine; i <= range.endLine; i++) { 3809 addHighlightYank(KTextEditor::Range(i, range.startColumn, i, range.endColumn)); 3810 } 3811 } else { 3812 addHighlightYank(KTextEditor::Range(range.startLine, range.startColumn, range.endLine, range.endColumn)); 3813 } 3814 } 3815 3816 void NormalViMode::addHighlightYank(KTextEditor::Range yankRange) 3817 { 3818 KTextEditor::MovingRange *highlightedYank = m_view->doc()->newMovingRange(yankRange, Kate::TextRange::DoNotExpand); 3819 highlightedYank->setView(m_view); // show only in this view 3820 highlightedYank->setAttributeOnlyForViews(true); 3821 // use z depth defined in moving ranges interface 3822 highlightedYank->setZDepth(-10000.0); 3823 highlightedYank->setAttribute(m_highlightYankAttribute); 3824 3825 highlightedYankForDocument().insert(highlightedYank); 3826 } 3827 3828 void NormalViMode::clearYankHighlight() 3829 { 3830 QSet<KTextEditor::MovingRange *> &pHighlightedYanks = highlightedYankForDocument(); 3831 qDeleteAll(pHighlightedYanks); 3832 pHighlightedYanks.clear(); 3833 } 3834 3835 void NormalViMode::aboutToDeleteMovingInterfaceContent() 3836 { 3837 QSet<KTextEditor::MovingRange *> &pHighlightedYanks = highlightedYankForDocument(); 3838 // Prevent double-deletion in case this NormalMode is deleted. 3839 pHighlightedYanks.clear(); 3840 } 3841 3842 QSet<KTextEditor::MovingRange *> &NormalViMode::highlightedYankForDocument() 3843 { 3844 // Work around the fact that both Normal and Visual mode will have their own m_highlightedYank - 3845 // make Normal's the canonical one. 3846 return m_viInputModeManager->getViNormalMode()->m_highlightedYanks; 3847 } 3848 3849 bool NormalViMode::waitingForRegisterOrCharToSearch() 3850 { 3851 // r, q, @ are never preceded by operators. There will always be a keys size of 1 for them. 3852 // f, t, F, T can be preceded by a delete/replace/yank/indent operator. size = 2 in that case. 3853 // f, t, F, T can be preceded by 'g' case/formatting operators. size = 3 in that case. 3854 const int keysSize = m_keys.size(); 3855 if (keysSize < 1) { 3856 // Just being defensive there. 3857 return false; 3858 } 3859 if (keysSize > 1) { 3860 // Multi-letter operation. 3861 QChar cPrefix = m_keys[0]; 3862 if (keysSize == 2) { 3863 // delete/replace/yank/indent operator? 3864 if (cPrefix != QLatin1Char('c') && cPrefix != QLatin1Char('d') && cPrefix != QLatin1Char('y') && cPrefix != QLatin1Char('=') 3865 && cPrefix != QLatin1Char('>') && cPrefix != QLatin1Char('<')) { 3866 return false; 3867 } 3868 } else if (keysSize == 3) { 3869 // We need to look deeper. Is it a g motion? 3870 QChar cNextfix = m_keys[1]; 3871 if (cPrefix != QLatin1Char('g') 3872 || (cNextfix != QLatin1Char('U') && cNextfix != QLatin1Char('u') && cNextfix != QLatin1Char('~') && cNextfix != QLatin1Char('q') 3873 && cNextfix != QLatin1Char('w') && cNextfix != QLatin1Char('@'))) { 3874 return false; 3875 } 3876 } else { 3877 return false; 3878 } 3879 } 3880 3881 QChar ch = m_keys[keysSize - 1]; 3882 return (ch == QLatin1Char('f') || ch == QLatin1Char('t') || ch == QLatin1Char('F') 3883 || ch == QLatin1Char('T') 3884 // c/d prefix unapplicable for the following cases. 3885 || (keysSize == 1 && (ch == QLatin1Char('r') || ch == QLatin1Char('q') || ch == QLatin1Char('@')))); 3886 } 3887 3888 void NormalViMode::textInserted(KTextEditor::Document *document, KTextEditor::Range range) 3889 { 3890 if (m_viInputModeManager->view()->viewInputMode() != KTextEditor::View::ViInputMode) { 3891 return; 3892 } 3893 3894 Q_UNUSED(document) 3895 const bool isInsertReplaceMode = 3896 (m_viInputModeManager->getCurrentViMode() == ViMode::InsertMode || m_viInputModeManager->getCurrentViMode() == ViMode::ReplaceMode); 3897 const bool continuesInsertion = range.start().line() == m_currentChangeEndMarker.line() && range.start().column() == m_currentChangeEndMarker.column(); 3898 const bool beginsWithNewline = doc()->text(range).at(0) == QLatin1Char('\n'); 3899 if (!continuesInsertion) { 3900 KTextEditor::Cursor newBeginMarkerPos = range.start(); 3901 if (beginsWithNewline && !isInsertReplaceMode) { 3902 // Presumably a linewise paste, in which case we ignore the leading '\n' 3903 newBeginMarkerPos = KTextEditor::Cursor(newBeginMarkerPos.line() + 1, 0); 3904 } 3905 m_viInputModeManager->marks()->setStartEditYanked(newBeginMarkerPos); 3906 } 3907 m_viInputModeManager->marks()->setLastChange(range.start()); 3908 KTextEditor::Cursor editEndMarker = range.end(); 3909 if (!isInsertReplaceMode) { 3910 editEndMarker.setColumn(editEndMarker.column() - 1); 3911 } 3912 m_viInputModeManager->marks()->setFinishEditYanked(editEndMarker); 3913 m_currentChangeEndMarker = range.end(); 3914 if (m_isUndo) { 3915 const bool addsMultipleLines = range.start().line() != range.end().line(); 3916 m_viInputModeManager->marks()->setStartEditYanked(KTextEditor::Cursor(m_viInputModeManager->marks()->getStartEditYanked().line(), 0)); 3917 if (addsMultipleLines) { 3918 m_viInputModeManager->marks()->setFinishEditYanked(KTextEditor::Cursor(m_viInputModeManager->marks()->getFinishEditYanked().line() + 1, 0)); 3919 m_viInputModeManager->marks()->setLastChange(KTextEditor::Cursor(m_viInputModeManager->marks()->getLastChange().line() + 1, 0)); 3920 } else { 3921 m_viInputModeManager->marks()->setFinishEditYanked(KTextEditor::Cursor(m_viInputModeManager->marks()->getFinishEditYanked().line(), 0)); 3922 m_viInputModeManager->marks()->setLastChange(KTextEditor::Cursor(m_viInputModeManager->marks()->getLastChange().line(), 0)); 3923 } 3924 } 3925 } 3926 3927 void NormalViMode::textRemoved(KTextEditor::Document *document, KTextEditor::Range range) 3928 { 3929 if (m_viInputModeManager->view()->viewInputMode() != KTextEditor::View::ViInputMode) { 3930 return; 3931 } 3932 3933 Q_UNUSED(document); 3934 const bool isInsertReplaceMode = 3935 (m_viInputModeManager->getCurrentViMode() == ViMode::InsertMode || m_viInputModeManager->getCurrentViMode() == ViMode::ReplaceMode); 3936 m_viInputModeManager->marks()->setLastChange(range.start()); 3937 if (!isInsertReplaceMode) { 3938 // Don't go resetting [ just because we did a Ctrl-h! 3939 m_viInputModeManager->marks()->setStartEditYanked(range.start()); 3940 } else { 3941 // Don't go disrupting our continued insertion just because we did a Ctrl-h! 3942 m_currentChangeEndMarker = range.start(); 3943 } 3944 m_viInputModeManager->marks()->setFinishEditYanked(range.start()); 3945 if (m_isUndo) { 3946 // Slavishly follow Vim's weird rules: if an undo removes several lines, then all markers should 3947 // be at the beginning of the line after the last line removed, else they should at the beginning 3948 // of the line above that. 3949 const int markerLineAdjustment = (range.start().line() != range.end().line()) ? 1 : 0; 3950 m_viInputModeManager->marks()->setStartEditYanked( 3951 KTextEditor::Cursor(m_viInputModeManager->marks()->getStartEditYanked().line() + markerLineAdjustment, 0)); 3952 m_viInputModeManager->marks()->setFinishEditYanked( 3953 KTextEditor::Cursor(m_viInputModeManager->marks()->getFinishEditYanked().line() + markerLineAdjustment, 0)); 3954 m_viInputModeManager->marks()->setLastChange(KTextEditor::Cursor(m_viInputModeManager->marks()->getLastChange().line() + markerLineAdjustment, 0)); 3955 } 3956 } 3957 3958 void NormalViMode::undoBeginning() 3959 { 3960 m_isUndo = true; 3961 } 3962 3963 void NormalViMode::undoEnded() 3964 { 3965 m_isUndo = false; 3966 } 3967 3968 bool NormalViMode::executeKateCommand(const QString &command) 3969 { 3970 KTextEditor::Command *p = KateCmd::self()->queryCommand(command); 3971 3972 if (!p) { 3973 return false; 3974 } 3975 3976 QString msg; 3977 return p->exec(m_view, command, msg); 3978 } 3979 3980 #define ADDCMD(STR, FUNC, FLGS) Command(QStringLiteral(STR), &NormalViMode::FUNC, FLGS) 3981 3982 #define ADDMOTION(STR, FUNC, FLGS) Motion(QStringLiteral(STR), &NormalViMode::FUNC, FLGS) 3983 3984 const std::vector<Command> &NormalViMode::commands() 3985 { 3986 // init once, is expensive 3987 static const std::vector<Command> global{ 3988 ADDCMD("a", commandEnterInsertModeAppend, IS_CHANGE), 3989 ADDCMD("A", commandEnterInsertModeAppendEOL, IS_CHANGE), 3990 ADDCMD("i", commandEnterInsertMode, IS_CHANGE), 3991 ADDCMD("<insert>", commandEnterInsertMode, IS_CHANGE), 3992 ADDCMD("I", commandEnterInsertModeBeforeFirstNonBlankInLine, IS_CHANGE), 3993 ADDCMD("gi", commandEnterInsertModeLast, IS_CHANGE), 3994 ADDCMD("v", commandEnterVisualMode, 0), 3995 ADDCMD("V", commandEnterVisualLineMode, 0), 3996 ADDCMD("<c-v>", commandEnterVisualBlockMode, 0), 3997 ADDCMD("gv", commandReselectVisual, SHOULD_NOT_RESET), 3998 ADDCMD("o", commandOpenNewLineUnder, IS_CHANGE), 3999 ADDCMD("O", commandOpenNewLineOver, IS_CHANGE), 4000 ADDCMD("J", commandJoinLines, IS_CHANGE), 4001 ADDCMD("c", commandChange, IS_CHANGE | NEEDS_MOTION), 4002 ADDCMD("C", commandChangeToEOL, IS_CHANGE), 4003 ADDCMD("cc", commandChangeLine, IS_CHANGE), 4004 ADDCMD("s", commandSubstituteChar, IS_CHANGE), 4005 ADDCMD("S", commandSubstituteLine, IS_CHANGE), 4006 ADDCMD("dd", commandDeleteLine, IS_CHANGE), 4007 ADDCMD("d", commandDelete, IS_CHANGE | NEEDS_MOTION), 4008 ADDCMD("D", commandDeleteToEOL, IS_CHANGE), 4009 ADDCMD("x", commandDeleteChar, IS_CHANGE), 4010 ADDCMD("<delete>", commandDeleteChar, IS_CHANGE), 4011 ADDCMD("X", commandDeleteCharBackward, IS_CHANGE), 4012 ADDCMD("gu", commandMakeLowercase, IS_CHANGE | NEEDS_MOTION), 4013 ADDCMD("guu", commandMakeLowercaseLine, IS_CHANGE), 4014 ADDCMD("gU", commandMakeUppercase, IS_CHANGE | NEEDS_MOTION), 4015 ADDCMD("gUU", commandMakeUppercaseLine, IS_CHANGE), 4016 ADDCMD("y", commandYank, NEEDS_MOTION), 4017 ADDCMD("yy", commandYankLine, 0), 4018 ADDCMD("Y", commandYankToEOL, 0), 4019 ADDCMD("p", commandPaste, IS_CHANGE), 4020 ADDCMD("P", commandPasteBefore, IS_CHANGE), 4021 ADDCMD("gp", commandgPaste, IS_CHANGE), 4022 ADDCMD("gP", commandgPasteBefore, IS_CHANGE), 4023 ADDCMD("]p", commandIndentedPaste, IS_CHANGE), 4024 ADDCMD("[p", commandIndentedPasteBefore, IS_CHANGE), 4025 ADDCMD("r.", commandReplaceCharacter, IS_CHANGE | REGEX_PATTERN), 4026 ADDCMD("R", commandEnterReplaceMode, IS_CHANGE), 4027 ADDCMD(":", commandSwitchToCmdLine, 0), 4028 ADDCMD("u", commandUndo, 0), 4029 ADDCMD("<c-r>", commandRedo, 0), 4030 ADDCMD("U", commandRedo, 0), 4031 ADDCMD("m.", commandSetMark, REGEX_PATTERN), 4032 ADDCMD(">>", commandIndentLine, IS_CHANGE), 4033 ADDCMD("<<", commandUnindentLine, IS_CHANGE), 4034 ADDCMD(">", commandIndentLines, IS_CHANGE | NEEDS_MOTION), 4035 ADDCMD("<", commandUnindentLines, IS_CHANGE | NEEDS_MOTION), 4036 ADDCMD("<c-f>", commandScrollPageDown, 0), 4037 ADDCMD("<pagedown>", commandScrollPageDown, 0), 4038 ADDCMD("<c-b>", commandScrollPageUp, 0), 4039 ADDCMD("<pageup>", commandScrollPageUp, 0), 4040 ADDCMD("<c-u>", commandScrollHalfPageUp, 0), 4041 ADDCMD("<c-d>", commandScrollHalfPageDown, 0), 4042 ADDCMD("z.", commandCenterViewOnNonBlank, 0), 4043 ADDCMD("zz", commandCenterViewOnCursor, 0), 4044 ADDCMD("z<return>", commandTopViewOnNonBlank, 0), 4045 ADDCMD("zt", commandTopViewOnCursor, 0), 4046 ADDCMD("z-", commandBottomViewOnNonBlank, 0), 4047 ADDCMD("zb", commandBottomViewOnCursor, 0), 4048 ADDCMD("ga", commandPrintCharacterCode, SHOULD_NOT_RESET), 4049 ADDCMD(".", commandRepeatLastChange, 0), 4050 ADDCMD("==", commandAlignLine, IS_CHANGE), 4051 ADDCMD("=", commandAlignLines, IS_CHANGE | NEEDS_MOTION), 4052 ADDCMD("~", commandChangeCase, IS_CHANGE), 4053 ADDCMD("g~", commandChangeCaseRange, IS_CHANGE | NEEDS_MOTION), 4054 ADDCMD("g~~", commandChangeCaseLine, IS_CHANGE), 4055 ADDCMD("<c-a>", commandAddToNumber, IS_CHANGE), 4056 ADDCMD("<c-x>", commandSubtractFromNumber, IS_CHANGE), 4057 ADDCMD("<c-o>", commandGoToPrevJump, CAN_LAND_INSIDE_FOLDING_RANGE), 4058 ADDCMD("<c-i>", commandGoToNextJump, CAN_LAND_INSIDE_FOLDING_RANGE), 4059 4060 ADDCMD("<c-w>h", commandSwitchToLeftView, 0), 4061 ADDCMD("<c-w><c-h>", commandSwitchToLeftView, 0), 4062 ADDCMD("<c-w><left>", commandSwitchToLeftView, 0), 4063 ADDCMD("<c-w>j", commandSwitchToDownView, 0), 4064 ADDCMD("<c-w><c-j>", commandSwitchToDownView, 0), 4065 ADDCMD("<c-w><down>", commandSwitchToDownView, 0), 4066 ADDCMD("<c-w>k", commandSwitchToUpView, 0), 4067 ADDCMD("<c-w><c-k>", commandSwitchToUpView, 0), 4068 ADDCMD("<c-w><up>", commandSwitchToUpView, 0), 4069 ADDCMD("<c-w>l", commandSwitchToRightView, 0), 4070 ADDCMD("<c-w><c-l>", commandSwitchToRightView, 0), 4071 ADDCMD("<c-w><right>", commandSwitchToRightView, 0), 4072 ADDCMD("<c-w>w", commandSwitchToNextView, 0), 4073 ADDCMD("<c-w><c-w>", commandSwitchToNextView, 0), 4074 4075 ADDCMD("<c-w>s", commandSplitHoriz, 0), 4076 ADDCMD("<c-w>S", commandSplitHoriz, 0), 4077 ADDCMD("<c-w><c-s>", commandSplitHoriz, 0), 4078 ADDCMD("<c-w>v", commandSplitVert, 0), 4079 ADDCMD("<c-w><c-v>", commandSplitVert, 0), 4080 ADDCMD("<c-w>c", commandCloseView, 0), 4081 4082 ADDCMD("gt", commandSwitchToNextTab, 0), 4083 ADDCMD("gT", commandSwitchToPrevTab, 0), 4084 4085 ADDCMD("gqq", commandFormatLine, IS_CHANGE), 4086 ADDCMD("gq", commandFormatLines, IS_CHANGE | NEEDS_MOTION), 4087 4088 ADDCMD("zo", commandExpandLocal, 0), 4089 ADDCMD("zc", commandCollapseLocal, 0), 4090 ADDCMD("za", commandToggleRegionVisibility, 0), 4091 ADDCMD("zr", commandExpandAll, 0), 4092 ADDCMD("zm", commandCollapseToplevelNodes, 0), 4093 4094 ADDCMD("q.", commandStartRecordingMacro, REGEX_PATTERN), 4095 ADDCMD("@.", commandReplayMacro, REGEX_PATTERN), 4096 4097 ADDCMD("ZZ", commandCloseWrite, 0), 4098 ADDCMD("ZQ", commandCloseNocheck, 0), 4099 }; 4100 return global; 4101 } 4102 4103 const std::vector<Motion> &NormalViMode::motions() 4104 { 4105 // init once, is expensive 4106 static const std::vector<Motion> global{ 4107 // regular motions 4108 ADDMOTION("h", motionLeft, 0), 4109 ADDMOTION("<left>", motionLeft, 0), 4110 ADDMOTION("<backspace>", motionLeft, 0), 4111 ADDMOTION("j", motionDown, 0), 4112 ADDMOTION("<down>", motionDown, 0), 4113 ADDMOTION("<enter>", motionDownToFirstNonBlank, 0), 4114 ADDMOTION("<return>", motionDownToFirstNonBlank, 0), 4115 ADDMOTION("k", motionUp, 0), 4116 ADDMOTION("<up>", motionUp, 0), 4117 ADDMOTION("-", motionUpToFirstNonBlank, 0), 4118 ADDMOTION("+", motionDownToFirstNonBlank, 0), 4119 ADDMOTION("l", motionRight, 0), 4120 ADDMOTION("<right>", motionRight, 0), 4121 ADDMOTION(" ", motionRight, 0), 4122 ADDMOTION("$", motionToEOL, 0), 4123 ADDMOTION("g_", motionToLastNonBlank, 0), 4124 ADDMOTION("<end>", motionToEOL, 0), 4125 ADDMOTION("0", motionToColumn0, 0), 4126 ADDMOTION("<home>", motionToColumn0, 0), 4127 ADDMOTION("^", motionToFirstCharacterOfLine, 0), 4128 ADDMOTION("f.", motionFindChar, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE), 4129 ADDMOTION("F.", motionFindCharBackward, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE), 4130 ADDMOTION("t.", motionToChar, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE), 4131 ADDMOTION("T.", motionToCharBackward, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE), 4132 ADDMOTION(";", motionRepeatlastTF, CAN_LAND_INSIDE_FOLDING_RANGE), 4133 ADDMOTION(",", motionRepeatlastTFBackward, CAN_LAND_INSIDE_FOLDING_RANGE), 4134 ADDMOTION("n", motionFindNext, CAN_LAND_INSIDE_FOLDING_RANGE), 4135 ADDMOTION("N", motionFindPrev, CAN_LAND_INSIDE_FOLDING_RANGE), 4136 ADDMOTION("gg", motionToLineFirst, 0), 4137 ADDMOTION("G", motionToLineLast, 0), 4138 ADDMOTION("w", motionWordForward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4139 ADDMOTION("W", motionWORDForward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4140 ADDMOTION("<c-right>", motionWordForward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4141 ADDMOTION("<c-left>", motionWordBackward, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4142 ADDMOTION("b", motionWordBackward, CAN_LAND_INSIDE_FOLDING_RANGE), 4143 ADDMOTION("B", motionWORDBackward, CAN_LAND_INSIDE_FOLDING_RANGE), 4144 ADDMOTION("e", motionToEndOfWord, CAN_LAND_INSIDE_FOLDING_RANGE), 4145 ADDMOTION("E", motionToEndOfWORD, CAN_LAND_INSIDE_FOLDING_RANGE), 4146 ADDMOTION("ge", motionToEndOfPrevWord, CAN_LAND_INSIDE_FOLDING_RANGE), 4147 ADDMOTION("gE", motionToEndOfPrevWORD, CAN_LAND_INSIDE_FOLDING_RANGE), 4148 ADDMOTION("|", motionToScreenColumn, 0), 4149 ADDMOTION("%", motionToMatchingItem, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4150 ADDMOTION("`[a-zA-Z^><\\.\\[\\]]", motionToMark, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE), 4151 ADDMOTION("'[a-zA-Z^><]", motionToMarkLine, REGEX_PATTERN | CAN_LAND_INSIDE_FOLDING_RANGE), 4152 ADDMOTION("[[", motionToPreviousBraceBlockStart, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4153 ADDMOTION("]]", motionToNextBraceBlockStart, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4154 ADDMOTION("[]", motionToPreviousBraceBlockEnd, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4155 ADDMOTION("][", motionToNextBraceBlockEnd, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4156 ADDMOTION("*", motionToNextOccurrence, CAN_LAND_INSIDE_FOLDING_RANGE), 4157 ADDMOTION("#", motionToPrevOccurrence, CAN_LAND_INSIDE_FOLDING_RANGE), 4158 ADDMOTION("H", motionToFirstLineOfWindow, 0), 4159 ADDMOTION("M", motionToMiddleLineOfWindow, 0), 4160 ADDMOTION("L", motionToLastLineOfWindow, 0), 4161 ADDMOTION("gj", motionToNextVisualLine, 0), 4162 ADDMOTION("g<down>", motionToNextVisualLine, 0), 4163 ADDMOTION("gk", motionToPrevVisualLine, 0), 4164 ADDMOTION("g<up>", motionToPrevVisualLine, 0), 4165 ADDMOTION("(", motionToPreviousSentence, CAN_LAND_INSIDE_FOLDING_RANGE), 4166 ADDMOTION(")", motionToNextSentence, CAN_LAND_INSIDE_FOLDING_RANGE), 4167 ADDMOTION("{", motionToBeforeParagraph, CAN_LAND_INSIDE_FOLDING_RANGE), 4168 ADDMOTION("}", motionToAfterParagraph, CAN_LAND_INSIDE_FOLDING_RANGE), 4169 4170 // text objects 4171 ADDMOTION("iw", textObjectInnerWord, 0), 4172 ADDMOTION("aw", textObjectAWord, IS_NOT_LINEWISE), 4173 ADDMOTION("iW", textObjectInnerWORD, 0), 4174 ADDMOTION("aW", textObjectAWORD, IS_NOT_LINEWISE), 4175 ADDMOTION("is", textObjectInnerSentence, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4176 ADDMOTION("as", textObjectASentence, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4177 ADDMOTION("ip", textObjectInnerParagraph, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4178 ADDMOTION("ap", textObjectAParagraph, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4179 ADDMOTION("i\"", textObjectInnerQuoteDouble, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4180 ADDMOTION("a\"", textObjectAQuoteDouble, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4181 ADDMOTION("i'", textObjectInnerQuoteSingle, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4182 ADDMOTION("a'", textObjectAQuoteSingle, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4183 ADDMOTION("i`", textObjectInnerBackQuote, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4184 ADDMOTION("a`", textObjectABackQuote, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4185 ADDMOTION("i[()b]", textObjectInnerParen, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4186 ADDMOTION("a[()b]", textObjectAParen, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4187 ADDMOTION("i[{}B]", textObjectInnerCurlyBracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4188 ADDMOTION("a[{}B]", textObjectACurlyBracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4189 ADDMOTION("i[><]", textObjectInnerInequalitySign, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4190 ADDMOTION("a[><]", textObjectAInequalitySign, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4191 ADDMOTION("i[\\[\\]]", textObjectInnerBracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4192 ADDMOTION("a[\\[\\]]", textObjectABracket, REGEX_PATTERN | IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4193 ADDMOTION("i,", textObjectInnerComma, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4194 ADDMOTION("a,", textObjectAComma, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4195 4196 ADDMOTION("/<enter>", motionToIncrementalSearchMatch, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4197 ADDMOTION("?<enter>", motionToIncrementalSearchMatch, IS_NOT_LINEWISE | CAN_LAND_INSIDE_FOLDING_RANGE), 4198 }; 4199 return global; 4200 }