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