File indexing completed on 2024-05-12 11:58:32
0001 /* 0002 SPDX-FileCopyrightText: 2008-2009 Erlend Hamberg <ehamberg@gmail.com> 0003 SPDX-FileCopyrightText: 2009 Paul Gideon Dann <pdgiddie@gmail.com> 0004 SPDX-FileCopyrightText: 2011 Svyatoslav Kuzmich <svatoslav1@gmail.com> 0005 SPDX-FileCopyrightText: 2012-2013 Simon St James <kdedevel@etotheipiplusone.com> 0006 0007 SPDX-License-Identifier: LGPL-2.0-or-later 0008 */ 0009 0010 #include "kateconfig.h" 0011 #include "katedocument.h" 0012 #include "kateglobal.h" 0013 #include "katelayoutcache.h" 0014 #include "katerenderer.h" 0015 #include "kateviewinternal.h" 0016 #include "kateviinputmode.h" 0017 #include <vimode/globalstate.h> 0018 #include <vimode/inputmodemanager.h> 0019 #include <vimode/jumps.h> 0020 #include <vimode/lastchangerecorder.h> 0021 #include <vimode/marks.h> 0022 #include <vimode/modes/modebase.h> 0023 #include <vimode/modes/normalvimode.h> 0024 #include <vimode/modes/replacevimode.h> 0025 #include <vimode/modes/visualvimode.h> 0026 #include <vimode/registers.h> 0027 #include <vimode/searcher.h> 0028 0029 #include <KLocalizedString> 0030 #include <QRegularExpression> 0031 #include <QString> 0032 0033 using namespace KateVi; 0034 0035 // TODO: the "previous word/WORD [end]" methods should be optimized. now they're being called in a 0036 // loop and all calculations done up to finding a match are trown away when called with a count > 1 0037 // because they will simply be called again from the last found position. 0038 // They should take the count as a parameter and collect the positions in a QList, then return 0039 // element (count - 1) 0040 0041 //////////////////////////////////////////////////////////////////////////////// 0042 // HELPER METHODS 0043 //////////////////////////////////////////////////////////////////////////////// 0044 0045 void ModeBase::yankToClipBoard(QChar chosen_register, const QString &text) 0046 { 0047 // only yank to the clipboard if no register was specified, 0048 // textlength > 1 and there is something else then whitespace 0049 if ((chosen_register == QLatin1Char('0') || chosen_register == QLatin1Char('-') || chosen_register == PrependNumberedRegister) && text.length() > 1 0050 && !text.trimmed().isEmpty()) { 0051 KTextEditor::EditorPrivate::self()->copyToClipboard(text, m_view->doc()->url().fileName()); 0052 } 0053 } 0054 0055 bool ModeBase::deleteRange(Range &r, OperationMode mode, bool addToRegister) 0056 { 0057 r.normalize(); 0058 bool res = false; 0059 const QString removedText = getRange(r, mode); 0060 0061 if (mode == LineWise) { 0062 doc()->editStart(); 0063 for (int i = 0; i < r.endLine - r.startLine + 1; i++) { 0064 res = doc()->removeLine(r.startLine); 0065 } 0066 doc()->editEnd(); 0067 } else { 0068 res = doc()->removeText(r.toEditorRange(), mode == Block); 0069 } 0070 0071 // the UnnamedRegister here is only a placeholder to signify that no register was selected 0072 // this is needed because the fallback register depends on whether the deleted text spans a line/lines 0073 QChar chosenRegister = getChosenRegister(UnnamedRegister); 0074 if (addToRegister) { 0075 fillRegister(chosenRegister, removedText, mode); 0076 } 0077 0078 const QChar lastChar = removedText.count() > 0 ? removedText.back() : QLatin1Char('\0'); 0079 if (chosenRegister != BlackHoleRegister && (r.startLine != r.endLine || lastChar == QLatin1Char('\n') || lastChar == QLatin1Char('\r'))) { 0080 // for deletes spanning a line/lines, always prepend to the numbered registers 0081 fillRegister(PrependNumberedRegister, removedText, mode); 0082 chosenRegister = PrependNumberedRegister; 0083 } else if (chosenRegister == UnnamedRegister) { 0084 // only set the SmallDeleteRegister when no register was selected 0085 fillRegister(SmallDeleteRegister, removedText, mode); 0086 chosenRegister = SmallDeleteRegister; 0087 } 0088 yankToClipBoard(chosenRegister, removedText); 0089 0090 return res; 0091 } 0092 0093 const QString ModeBase::getRange(Range &r, OperationMode mode) const 0094 { 0095 r.normalize(); 0096 QString s; 0097 0098 if (mode == LineWise) { 0099 r.startColumn = 0; 0100 r.endColumn = getLine(r.endLine).length(); 0101 } 0102 0103 if (r.motionType == InclusiveMotion) { 0104 r.endColumn++; 0105 } 0106 0107 KTextEditor::Range range = r.toEditorRange(); 0108 if (mode == LineWise) { 0109 s = doc()->textLines(range).join(QLatin1Char('\n')); 0110 s.append(QLatin1Char('\n')); 0111 } else if (mode == Block) { 0112 s = doc()->text(range, true); 0113 } else { 0114 s = doc()->text(range); 0115 } 0116 0117 return s; 0118 } 0119 0120 const QString ModeBase::getLine(int line) const 0121 { 0122 return (line < 0) ? m_view->doc()->line(m_view->cursorPosition().line()) : doc()->line(line); 0123 } 0124 0125 const QChar ModeBase::getCharUnderCursor() const 0126 { 0127 KTextEditor::Cursor c(m_view->cursorPosition()); 0128 0129 QString line = getLine(c.line()); 0130 0131 if (line.length() == 0 && c.column() >= line.length()) { 0132 return QChar::Null; 0133 } 0134 0135 return line.at(c.column()); 0136 } 0137 0138 const QString ModeBase::getWordUnderCursor() const 0139 { 0140 return doc()->text(getWordRangeUnderCursor()); 0141 } 0142 0143 const KTextEditor::Range ModeBase::getWordRangeUnderCursor() const 0144 { 0145 KTextEditor::Cursor c(m_view->cursorPosition()); 0146 0147 // find first character that is a “word letter” and start the search there 0148 QChar ch = doc()->characterAt(c); 0149 int i = 0; 0150 while (!ch.isLetterOrNumber() && !ch.isMark() && ch != QLatin1Char('_') && m_extraWordCharacters.indexOf(ch) == -1) { 0151 // advance cursor one position 0152 c.setColumn(c.column() + 1); 0153 if (c.column() > doc()->lineLength(c.line())) { 0154 c.setColumn(0); 0155 c.setLine(c.line() + 1); 0156 if (c.line() == doc()->lines()) { 0157 return KTextEditor::Range::invalid(); 0158 } 0159 } 0160 0161 ch = doc()->characterAt(c); 0162 i++; // count characters that were advanced so we know where to start the search 0163 } 0164 0165 // move cursor the word (if cursor was placed on e.g. a paren, this will move 0166 // it to the right 0167 updateCursor(c); 0168 0169 KTextEditor::Cursor c1 = findPrevWordStart(c.line(), c.column() + 1 + i, true); 0170 KTextEditor::Cursor c2 = findWordEnd(c1.line(), c1.column() + i - 1, true); 0171 c2.setColumn(c2.column() + 1); 0172 0173 return KTextEditor::Range(c1, c2); 0174 } 0175 0176 KTextEditor::Cursor ModeBase::findNextWordStart(int fromLine, int fromColumn, bool onlyCurrentLine) const 0177 { 0178 QString line = getLine(fromLine); 0179 0180 // the start of word pattern need to take m_extraWordCharacters into account if defined 0181 QString startOfWordPattern = QStringLiteral("\\b(\\w"); 0182 if (m_extraWordCharacters.length() > 0) { 0183 startOfWordPattern.append(QLatin1String("|[") + m_extraWordCharacters + QLatin1Char(']')); 0184 } 0185 startOfWordPattern.append(QLatin1Char(')')); 0186 0187 const QRegularExpression startOfWord(startOfWordPattern, QRegularExpression::UseUnicodePropertiesOption); // start of a word 0188 static const QRegularExpression nonSpaceAfterSpace(QStringLiteral("\\s\\S"), QRegularExpression::UseUnicodePropertiesOption); // non-space right after space 0189 static const QRegularExpression nonWordAfterWord(QStringLiteral("\\b(?!\\s)\\W"), QRegularExpression::UseUnicodePropertiesOption); // word-boundary followed by a non-word which is not a space 0190 0191 int l = fromLine; 0192 int c = fromColumn; 0193 0194 bool found = false; 0195 0196 while (!found) { 0197 int c1 = line.indexOf(startOfWord, c + 1); 0198 int c2 = line.indexOf(nonSpaceAfterSpace, c); 0199 int c3 = line.indexOf(nonWordAfterWord, c + 1); 0200 0201 if (c1 == -1 && c2 == -1 && c3 == -1) { 0202 if (onlyCurrentLine) { 0203 return KTextEditor::Cursor::invalid(); 0204 } else if (l >= doc()->lines() - 1) { 0205 c = qMax(line.length() - 1, 0); 0206 return KTextEditor::Cursor::invalid(); 0207 } else { 0208 c = 0; 0209 l++; 0210 0211 line = getLine(l); 0212 0213 if (line.length() == 0 || !line.at(c).isSpace()) { 0214 found = true; 0215 } 0216 0217 continue; 0218 } 0219 } 0220 0221 c2++; // the second regexp will match one character *before* the character we want to go to 0222 0223 if (c1 <= 0) { 0224 c1 = line.length() - 1; 0225 } 0226 if (c2 <= 0) { 0227 c2 = line.length() - 1; 0228 } 0229 if (c3 <= 0) { 0230 c3 = line.length() - 1; 0231 } 0232 0233 c = qMin(c1, qMin(c2, c3)); 0234 0235 found = true; 0236 } 0237 0238 return KTextEditor::Cursor(l, c); 0239 } 0240 0241 KTextEditor::Cursor ModeBase::findNextWORDStart(int fromLine, int fromColumn, bool onlyCurrentLine) const 0242 { 0243 QString line = getLine(); 0244 0245 int l = fromLine; 0246 int c = fromColumn; 0247 0248 bool found = false; 0249 static const QRegularExpression startOfWORD(QStringLiteral("\\s\\S"), QRegularExpression::UseUnicodePropertiesOption); 0250 0251 while (!found) { 0252 c = line.indexOf(startOfWORD, c); 0253 0254 if (c == -1) { 0255 if (onlyCurrentLine) { 0256 return KTextEditor::Cursor(l, c); 0257 } else if (l >= doc()->lines() - 1) { 0258 c = line.length() - 1; 0259 break; 0260 } else { 0261 c = 0; 0262 l++; 0263 0264 line = getLine(l); 0265 0266 if (line.length() == 0 || !line.at(c).isSpace()) { 0267 found = true; 0268 } 0269 0270 continue; 0271 } 0272 } else { 0273 c++; 0274 found = true; 0275 } 0276 } 0277 0278 return KTextEditor::Cursor(l, c); 0279 } 0280 0281 KTextEditor::Cursor ModeBase::findPrevWordEnd(int fromLine, int fromColumn, bool onlyCurrentLine) const 0282 { 0283 QString line = getLine(fromLine); 0284 0285 QString endOfWordPattern = QStringLiteral("\\S\\s|\\S$|\\S\\b|\\w\\W|^$"); 0286 0287 if (m_extraWordCharacters.length() > 0) { 0288 endOfWordPattern.append(QLatin1String("|[") + m_extraWordCharacters + QLatin1String("][^") + m_extraWordCharacters + QLatin1Char(']')); 0289 } 0290 0291 const QRegularExpression endOfWord(endOfWordPattern); 0292 0293 int l = fromLine; 0294 int c = fromColumn; 0295 0296 bool found = false; 0297 0298 while (!found) { 0299 int c1 = line.lastIndexOf(endOfWord, c - 1); 0300 0301 if (c1 != -1 && c - 1 != -1) { 0302 found = true; 0303 c = c1; 0304 } else { 0305 if (onlyCurrentLine) { 0306 return KTextEditor::Cursor::invalid(); 0307 } else if (l > 0) { 0308 line = getLine(--l); 0309 c = line.length(); 0310 0311 continue; 0312 } else { 0313 return KTextEditor::Cursor::invalid(); 0314 } 0315 } 0316 } 0317 0318 return KTextEditor::Cursor(l, c); 0319 } 0320 0321 KTextEditor::Cursor ModeBase::findPrevWORDEnd(int fromLine, int fromColumn, bool onlyCurrentLine) const 0322 { 0323 QString line = getLine(fromLine); 0324 0325 static const QRegularExpression endOfWORDPattern(QStringLiteral("\\S\\s|\\S$|^$"), QRegularExpression::UseUnicodePropertiesOption); 0326 0327 int l = fromLine; 0328 int c = fromColumn; 0329 0330 bool found = false; 0331 0332 while (!found) { 0333 int c1 = line.lastIndexOf(endOfWORDPattern, c - 1); 0334 0335 if (c1 != -1 && c - 1 != -1) { 0336 found = true; 0337 c = c1; 0338 } else { 0339 if (onlyCurrentLine) { 0340 return KTextEditor::Cursor::invalid(); 0341 } else if (l > 0) { 0342 line = getLine(--l); 0343 c = line.length(); 0344 0345 continue; 0346 } else { 0347 c = 0; 0348 return KTextEditor::Cursor::invalid(); 0349 } 0350 } 0351 } 0352 0353 return KTextEditor::Cursor(l, c); 0354 } 0355 0356 KTextEditor::Cursor ModeBase::findPrevWordStart(int fromLine, int fromColumn, bool onlyCurrentLine) const 0357 { 0358 QString line = getLine(fromLine); 0359 0360 // the start of word pattern need to take m_extraWordCharacters into account if defined 0361 QString startOfWordPattern = QStringLiteral("\\b(\\w"); 0362 if (m_extraWordCharacters.length() > 0) { 0363 startOfWordPattern.append(QLatin1String("|[") + m_extraWordCharacters + QLatin1Char(']')); 0364 } 0365 startOfWordPattern.append(QLatin1Char(')')); 0366 0367 const QRegularExpression startOfWord(startOfWordPattern, QRegularExpression::UseUnicodePropertiesOption); // start of a word 0368 static const QRegularExpression nonSpaceAfterSpace(QStringLiteral("\\s\\S"), QRegularExpression::UseUnicodePropertiesOption); // non-space right after space 0369 static const QRegularExpression nonWordAfterWord(QStringLiteral("\\b(?!\\s)\\W"), QRegularExpression::UseUnicodePropertiesOption); // word-boundary followed by a non-word which is not a space 0370 static const QRegularExpression startOfLine(QStringLiteral("^\\S"), QRegularExpression::UseUnicodePropertiesOption); // non-space at start of line 0371 0372 int l = fromLine; 0373 int c = fromColumn; 0374 0375 bool found = false; 0376 0377 while (!found) { 0378 int c1 = (c > 0) ? line.lastIndexOf(startOfWord, c - 1) : -1; 0379 int c2 = (c > 1) ? line.lastIndexOf(nonSpaceAfterSpace, c - 2) : -1; 0380 int c3 = (c > 0) ? line.lastIndexOf(nonWordAfterWord, c - 1) : -1; 0381 int c4 = (c > 0) ? line.lastIndexOf(startOfLine, c - 1) : -1; 0382 0383 if (c1 == -1 && c2 == -1 && c3 == -1 && c4 == -1) { 0384 if (onlyCurrentLine) { 0385 return KTextEditor::Cursor::invalid(); 0386 } else if (l <= 0) { 0387 return KTextEditor::Cursor::invalid(); 0388 } else { 0389 line = getLine(--l); 0390 c = line.length(); 0391 0392 if (line.length() == 0) { 0393 c = 0; 0394 found = true; 0395 } 0396 0397 continue; 0398 } 0399 } 0400 0401 c2++; // the second regexp will match one character *before* the character we want to go to 0402 0403 if (c1 <= 0) { 0404 c1 = 0; 0405 } 0406 if (c2 <= 0) { 0407 c2 = 0; 0408 } 0409 if (c3 <= 0) { 0410 c3 = 0; 0411 } 0412 if (c4 <= 0) { 0413 c4 = 0; 0414 } 0415 0416 c = qMax(c1, qMax(c2, qMax(c3, c4))); 0417 0418 found = true; 0419 } 0420 0421 return KTextEditor::Cursor(l, c); 0422 } 0423 0424 KTextEditor::Cursor ModeBase::findPrevWORDStart(int fromLine, int fromColumn, bool onlyCurrentLine) const 0425 { 0426 QString line = getLine(fromLine); 0427 0428 static const QRegularExpression startOfWORD(QStringLiteral("\\s\\S"), QRegularExpression::UseUnicodePropertiesOption); 0429 static const QRegularExpression startOfLineWORD(QStringLiteral("^\\S"), QRegularExpression::UseUnicodePropertiesOption); 0430 0431 int l = fromLine; 0432 int c = fromColumn; 0433 0434 bool found = false; 0435 0436 while (!found) { 0437 int c1 = (c > 1) ? line.lastIndexOf(startOfWORD, c - 2) : -1; 0438 int c2 = (c > 0) ? line.lastIndexOf(startOfLineWORD, c - 1) : -1; 0439 0440 if (c1 == -1 && c2 == -1) { 0441 if (onlyCurrentLine) { 0442 return KTextEditor::Cursor::invalid(); 0443 } else if (l <= 0) { 0444 return KTextEditor::Cursor::invalid(); 0445 } else { 0446 line = getLine(--l); 0447 c = line.length(); 0448 0449 if (line.length() == 0) { 0450 c = 0; 0451 found = true; 0452 } 0453 0454 continue; 0455 } 0456 } 0457 0458 c1++; // the startOfWORD pattern matches one character before the word 0459 0460 c = qMax(c1, c2); 0461 0462 if (c <= 0) { 0463 c = 0; 0464 } 0465 0466 found = true; 0467 } 0468 0469 return KTextEditor::Cursor(l, c); 0470 } 0471 0472 KTextEditor::Cursor ModeBase::findWordEnd(int fromLine, int fromColumn, bool onlyCurrentLine) const 0473 { 0474 QString line = getLine(fromLine); 0475 0476 QString endOfWordPattern = QStringLiteral("\\S\\s|\\S$|\\w\\W|\\S\\b"); 0477 0478 if (m_extraWordCharacters.length() > 0) { 0479 endOfWordPattern.append(QLatin1String("|[") + m_extraWordCharacters + QLatin1String("][^") + m_extraWordCharacters + QLatin1Char(']')); 0480 } 0481 0482 const QRegularExpression endOfWORD(endOfWordPattern); 0483 0484 int l = fromLine; 0485 int c = fromColumn; 0486 0487 bool found = false; 0488 0489 while (!found) { 0490 int c1 = line.indexOf(endOfWORD, c + 1); 0491 0492 if (c1 != -1) { 0493 found = true; 0494 c = c1; 0495 } else { 0496 if (onlyCurrentLine) { 0497 return KTextEditor::Cursor::invalid(); 0498 } else if (l >= doc()->lines() - 1) { 0499 c = line.length() - 1; 0500 return KTextEditor::Cursor::invalid(); 0501 } else { 0502 c = -1; 0503 line = getLine(++l); 0504 0505 continue; 0506 } 0507 } 0508 } 0509 0510 return KTextEditor::Cursor(l, c); 0511 } 0512 0513 KTextEditor::Cursor ModeBase::findWORDEnd(int fromLine, int fromColumn, bool onlyCurrentLine) const 0514 { 0515 QString line = getLine(fromLine); 0516 0517 static const QRegularExpression endOfWORD(QStringLiteral("\\S\\s|\\S$"), QRegularExpression::UseUnicodePropertiesOption); 0518 0519 int l = fromLine; 0520 int c = fromColumn; 0521 0522 bool found = false; 0523 0524 while (!found) { 0525 int c1 = line.indexOf(endOfWORD, c + 1); 0526 0527 if (c1 != -1) { 0528 found = true; 0529 c = c1; 0530 } else { 0531 if (onlyCurrentLine) { 0532 return KTextEditor::Cursor::invalid(); 0533 } else if (l >= doc()->lines() - 1) { 0534 c = line.length() - 1; 0535 return KTextEditor::Cursor::invalid(); 0536 } else { 0537 c = -1; 0538 line = getLine(++l); 0539 0540 continue; 0541 } 0542 } 0543 } 0544 0545 return KTextEditor::Cursor(l, c); 0546 } 0547 0548 Range innerRange(Range range, bool inner) 0549 { 0550 Range r = range; 0551 0552 if (inner) { 0553 const int columnDistance = qAbs(r.startColumn - r.endColumn); 0554 if ((r.startLine == r.endLine) && columnDistance == 1) { 0555 // Start and end are right next to each other; there is nothing inside them. 0556 return Range::invalid(); 0557 } 0558 r.startColumn++; 0559 r.endColumn--; 0560 } 0561 0562 return r; 0563 } 0564 0565 Range ModeBase::findSurroundingQuotes(const QChar &c, bool inner) const 0566 { 0567 KTextEditor::Cursor cursor(m_view->cursorPosition()); 0568 Range r; 0569 r.startLine = cursor.line(); 0570 r.endLine = cursor.line(); 0571 0572 QString line = doc()->line(cursor.line()); 0573 0574 // If cursor on the quote we should choose the best direction. 0575 if (line.at(cursor.column()) == c) { 0576 int attribute = m_view->doc()->kateTextLine(cursor.line())->attribute(cursor.column()); 0577 0578 // If at the beginning of the line - then we might search the end. 0579 if (doc()->kateTextLine(cursor.line())->attribute(cursor.column() + 1) == attribute 0580 && doc()->kateTextLine(cursor.line())->attribute(cursor.column() - 1) != attribute) { 0581 r.startColumn = cursor.column(); 0582 r.endColumn = line.indexOf(c, cursor.column() + 1); 0583 0584 return innerRange(r, inner); 0585 } 0586 0587 // If at the end of the line - then we might search the beginning. 0588 if (doc()->kateTextLine(cursor.line())->attribute(cursor.column() + 1) != attribute 0589 && doc()->kateTextLine(cursor.line())->attribute(cursor.column() - 1) == attribute) { 0590 r.startColumn = line.lastIndexOf(c, cursor.column() - 1); 0591 r.endColumn = cursor.column(); 0592 0593 return innerRange(r, inner); 0594 } 0595 // Try to search the quote to right 0596 int c1 = line.indexOf(c, cursor.column() + 1); 0597 if (c1 != -1) { 0598 r.startColumn = cursor.column(); 0599 r.endColumn = c1; 0600 0601 return innerRange(r, inner); 0602 } 0603 0604 // Try to search the quote to left 0605 int c2 = line.lastIndexOf(c, cursor.column() - 1); 0606 if (c2 != -1) { 0607 r.startColumn = c2; 0608 r.endColumn = cursor.column(); 0609 0610 return innerRange(r, inner); 0611 } 0612 0613 // Nothing found - give up :) 0614 return Range::invalid(); 0615 } 0616 0617 r.startColumn = line.lastIndexOf(c, cursor.column()); 0618 r.endColumn = line.indexOf(c, cursor.column()); 0619 0620 if (r.startColumn == -1 || r.endColumn == -1 || r.startColumn > r.endColumn) { 0621 return Range::invalid(); 0622 } 0623 0624 return innerRange(r, inner); 0625 } 0626 0627 Range ModeBase::findSurroundingBrackets(const QChar &c1, const QChar &c2, bool inner, const QChar &nested1, const QChar &nested2) const 0628 { 0629 KTextEditor::Cursor cursor(m_view->cursorPosition()); 0630 Range r(cursor, InclusiveMotion); 0631 int line = cursor.line(); 0632 int column = cursor.column(); 0633 int catalan; 0634 0635 // Chars should not differ. For equal chars use findSurroundingQuotes. 0636 Q_ASSERT(c1 != c2); 0637 0638 const QString &l = m_view->doc()->line(line); 0639 if (column < l.size() && l.at(column) == c2) { 0640 r.endLine = line; 0641 r.endColumn = column; 0642 } else { 0643 if (column < l.size() && l.at(column) == c1) { 0644 column++; 0645 } 0646 0647 for (catalan = 1; line < m_view->doc()->lines(); line++) { 0648 const QString &l = m_view->doc()->line(line); 0649 0650 for (; column < l.size(); column++) { 0651 const QChar &c = l.at(column); 0652 0653 if (c == nested1) { 0654 catalan++; 0655 } else if (c == nested2) { 0656 catalan--; 0657 } 0658 if (!catalan) { 0659 break; 0660 } 0661 } 0662 if (!catalan) { 0663 break; 0664 } 0665 column = 0; 0666 } 0667 0668 if (catalan != 0) { 0669 return Range::invalid(); 0670 } 0671 r.endLine = line; 0672 r.endColumn = column; 0673 } 0674 0675 // Same algorithm but backwards. 0676 line = cursor.line(); 0677 column = cursor.column(); 0678 0679 if (column < l.size() && l.at(column) == c1) { 0680 r.startLine = line; 0681 r.startColumn = column; 0682 } else { 0683 if (column < l.size() && l.at(column) == c2) { 0684 column--; 0685 } 0686 0687 for (catalan = 1; line >= 0; line--) { 0688 const QString &l = m_view->doc()->line(line); 0689 0690 for (; column >= 0; column--) { 0691 const QChar &c = l.at(column); 0692 0693 if (c == nested1) { 0694 catalan--; 0695 } else if (c == nested2) { 0696 catalan++; 0697 } 0698 if (!catalan) { 0699 break; 0700 } 0701 } 0702 if (!catalan || !line) { 0703 break; 0704 } 0705 column = m_view->doc()->line(line - 1).size() - 1; 0706 } 0707 if (catalan != 0) { 0708 return Range::invalid(); 0709 } 0710 r.startColumn = column; 0711 r.startLine = line; 0712 } 0713 0714 return innerRange(r, inner); 0715 } 0716 0717 Range ModeBase::findSurrounding(const QRegularExpression &c1, const QRegularExpression &c2, bool inner) const 0718 { 0719 KTextEditor::Cursor cursor(m_view->cursorPosition()); 0720 QString line = getLine(); 0721 0722 int col1 = line.lastIndexOf(c1, cursor.column()); 0723 int col2 = line.indexOf(c2, cursor.column()); 0724 0725 Range r(cursor.line(), col1, cursor.line(), col2, InclusiveMotion); 0726 0727 if (col1 == -1 || col2 == -1 || col1 > col2) { 0728 return Range::invalid(); 0729 } 0730 0731 if (inner) { 0732 r.startColumn++; 0733 r.endColumn--; 0734 } 0735 0736 return r; 0737 } 0738 0739 int ModeBase::findLineStartingWitchChar(const QChar &c, int count, bool forward) const 0740 { 0741 int line = m_view->cursorPosition().line(); 0742 int lines = doc()->lines(); 0743 int hits = 0; 0744 0745 if (forward) { 0746 line++; 0747 } else { 0748 line--; 0749 } 0750 0751 while (line < lines && line >= 0 && hits < count) { 0752 QString l = getLine(line); 0753 if (l.length() > 0 && l.at(0) == c) { 0754 hits++; 0755 } 0756 if (hits != count) { 0757 if (forward) { 0758 line++; 0759 } else { 0760 line--; 0761 } 0762 } 0763 } 0764 0765 if (hits == getCount()) { 0766 return line; 0767 } 0768 0769 return -1; 0770 } 0771 0772 void ModeBase::updateCursor(const KTextEditor::Cursor c) const 0773 { 0774 m_viInputModeManager->updateCursor(c); 0775 } 0776 0777 /** 0778 * @return the register given for the command. If no register was given, defaultReg is returned. 0779 */ 0780 QChar ModeBase::getChosenRegister(const QChar &defaultReg) const 0781 { 0782 return (m_register != QChar::Null) ? m_register : defaultReg; 0783 } 0784 0785 QString ModeBase::getRegisterContent(const QChar ®) 0786 { 0787 QString r = m_viInputModeManager->globalState()->registers()->getContent(reg); 0788 0789 if (r.isNull()) { 0790 error(i18n("Nothing in register %1", reg.toLower())); 0791 } 0792 0793 return r; 0794 } 0795 0796 OperationMode ModeBase::getRegisterFlag(const QChar ®) const 0797 { 0798 return m_viInputModeManager->globalState()->registers()->getFlag(reg); 0799 } 0800 0801 void ModeBase::fillRegister(const QChar ®, const QString &text, OperationMode flag) 0802 { 0803 m_viInputModeManager->globalState()->registers()->set(reg, text, flag); 0804 } 0805 0806 KTextEditor::Cursor ModeBase::getNextJump(KTextEditor::Cursor cursor) const 0807 { 0808 return m_viInputModeManager->jumps()->next(cursor); 0809 } 0810 0811 KTextEditor::Cursor ModeBase::getPrevJump(KTextEditor::Cursor cursor) const 0812 { 0813 return m_viInputModeManager->jumps()->prev(cursor); 0814 } 0815 0816 Range ModeBase::goLineDown() 0817 { 0818 return goLineUpDown(getCount()); 0819 } 0820 0821 Range ModeBase::goLineUp() 0822 { 0823 return goLineUpDown(-getCount()); 0824 } 0825 0826 /** 0827 * method for moving up or down one or more lines 0828 * note: the sticky column is always a virtual column 0829 */ 0830 Range ModeBase::goLineUpDown(int lines) 0831 { 0832 KTextEditor::Cursor c(m_view->cursorPosition()); 0833 Range r(c, InclusiveMotion); 0834 int tabstop = doc()->config()->tabWidth(); 0835 0836 // if in an empty document, just return 0837 if (lines == 0) { 0838 return r; 0839 } 0840 0841 r.endLine += lines; 0842 0843 // limit end line to be from line 0 through the last line 0844 if (r.endLine < 0) { 0845 r.endLine = 0; 0846 } else if (r.endLine > doc()->lines() - 1) { 0847 r.endLine = doc()->lines() - 1; 0848 } 0849 0850 Kate::TextLine startLine = doc()->plainKateTextLine(c.line()); 0851 Kate::TextLine endLine = doc()->plainKateTextLine(r.endLine); 0852 0853 int endLineLen = doc()->lineLength(r.endLine) - 1; 0854 0855 if (endLineLen < 0) { 0856 endLineLen = 0; 0857 } 0858 0859 int endLineLenVirt = endLine->toVirtualColumn(endLineLen, tabstop); 0860 int virtColumnStart = startLine->toVirtualColumn(c.column(), tabstop); 0861 0862 // if sticky column isn't set, set end column and set sticky column to its virtual column 0863 if (m_stickyColumn == -1) { 0864 r.endColumn = endLine->fromVirtualColumn(virtColumnStart, tabstop); 0865 m_stickyColumn = virtColumnStart; 0866 } else { 0867 // sticky is set - set end column to its value 0868 r.endColumn = endLine->fromVirtualColumn(m_stickyColumn, tabstop); 0869 } 0870 0871 // make sure end column won't be after the last column of a line 0872 if (r.endColumn > endLineLen) { 0873 r.endColumn = endLineLen; 0874 } 0875 0876 // if we move to a line shorter than the current column, go to its end 0877 if (virtColumnStart > endLineLenVirt) { 0878 r.endColumn = endLineLen; 0879 } 0880 0881 return r; 0882 } 0883 0884 Range ModeBase::goVisualLineUpDown(int lines) 0885 { 0886 KTextEditor::Cursor c(m_view->cursorPosition()); 0887 Range r(c, InclusiveMotion); 0888 int tabstop = doc()->config()->tabWidth(); 0889 0890 if (lines == 0) { 0891 // We're not moving anywhere. 0892 return r; 0893 } 0894 0895 KateLayoutCache *cache = m_viInputModeManager->inputAdapter()->layoutCache(); 0896 0897 // Work out the real and visual line pair of the beginning of the visual line we'd end up 0898 // on by moving lines visual lines. We ignore the column, for now. 0899 int finishVisualLine = cache->viewLine(m_view->cursorPosition()); 0900 int finishRealLine = m_view->cursorPosition().line(); 0901 int count = qAbs(lines); 0902 bool invalidPos = false; 0903 if (lines > 0) { 0904 // Find the beginning of the visual line "lines" visual lines down. 0905 while (count > 0) { 0906 finishVisualLine++; 0907 if (finishVisualLine >= cache->line(finishRealLine)->viewLineCount()) { 0908 finishRealLine++; 0909 finishVisualLine = 0; 0910 } 0911 if (finishRealLine >= doc()->lines()) { 0912 invalidPos = true; 0913 break; 0914 } 0915 count--; 0916 } 0917 } else { 0918 // Find the beginning of the visual line "lines" visual lines up. 0919 while (count > 0) { 0920 finishVisualLine--; 0921 if (finishVisualLine < 0) { 0922 finishRealLine--; 0923 if (finishRealLine < 0) { 0924 invalidPos = true; 0925 break; 0926 } 0927 finishVisualLine = cache->line(finishRealLine)->viewLineCount() - 1; 0928 } 0929 count--; 0930 } 0931 } 0932 if (invalidPos) { 0933 r.endLine = -1; 0934 r.endColumn = -1; 0935 return r; 0936 } 0937 0938 // We know the final (real) line ... 0939 r.endLine = finishRealLine; 0940 // ... now work out the final (real) column. 0941 0942 if (m_stickyColumn == -1 || !m_lastMotionWasVisualLineUpOrDown) { 0943 // Compute new sticky column. It is a *visual* sticky column. 0944 int startVisualLine = cache->viewLine(m_view->cursorPosition()); 0945 int startRealLine = m_view->cursorPosition().line(); 0946 const Kate::TextLine startLine = doc()->plainKateTextLine(c.line()); 0947 // Adjust for the fact that if the portion of the line before wrapping is indented, 0948 // the continuations are also "invisibly" (i.e. without any spaces in the text itself) indented. 0949 const bool isWrappedContinuation = (cache->textLayout(startRealLine, startVisualLine).lineLayout().lineNumber() != 0); 0950 const int numInvisibleIndentChars = 0951 isWrappedContinuation ? startLine->toVirtualColumn(cache->line(startRealLine)->textLine()->nextNonSpaceChar(0), tabstop) : 0; 0952 0953 const int realLineStartColumn = cache->textLayout(startRealLine, startVisualLine).startCol(); 0954 const int lineStartVirtualColumn = startLine->toVirtualColumn(realLineStartColumn, tabstop); 0955 const int visualColumnNoInvisibleIndent = startLine->toVirtualColumn(c.column(), tabstop) - lineStartVirtualColumn; 0956 m_stickyColumn = visualColumnNoInvisibleIndent + numInvisibleIndentChars; 0957 Q_ASSERT(m_stickyColumn >= 0); 0958 } 0959 0960 // The "real" (non-virtual) beginning of the current "line", which might be a wrapped continuation of a 0961 // "real" line. 0962 const int realLineStartColumn = cache->textLayout(finishRealLine, finishVisualLine).startCol(); 0963 const Kate::TextLine endLine = doc()->plainKateTextLine(r.endLine); 0964 // Adjust for the fact that if the portion of the line before wrapping is indented, 0965 // the continuations are also "invisibly" (i.e. without any spaces in the text itself) indented. 0966 const bool isWrappedContinuation = (cache->textLayout(finishRealLine, finishVisualLine).lineLayout().lineNumber() != 0); 0967 const int numInvisibleIndentChars = 0968 isWrappedContinuation ? endLine->toVirtualColumn(cache->line(finishRealLine)->textLine()->nextNonSpaceChar(0), tabstop) : 0; 0969 if (m_stickyColumn == (unsigned int)KateVi::EOL) { 0970 const int visualEndColumn = cache->textLayout(finishRealLine, finishVisualLine).lineLayout().textLength() - 1; 0971 r.endColumn = endLine->fromVirtualColumn(visualEndColumn + realLineStartColumn - numInvisibleIndentChars, tabstop); 0972 } else { 0973 // Algorithm: find the "real" column corresponding to the start of the line. Offset from that 0974 // until the "visual" column is equal to the "visual" sticky column. 0975 int realOffsetToVisualStickyColumn = 0; 0976 const int lineStartVirtualColumn = endLine->toVirtualColumn(realLineStartColumn, tabstop); 0977 while (true) { 0978 const int visualColumn = 0979 endLine->toVirtualColumn(realLineStartColumn + realOffsetToVisualStickyColumn, tabstop) - lineStartVirtualColumn + numInvisibleIndentChars; 0980 if (visualColumn >= m_stickyColumn) { 0981 break; 0982 } 0983 realOffsetToVisualStickyColumn++; 0984 } 0985 r.endColumn = realLineStartColumn + realOffsetToVisualStickyColumn; 0986 } 0987 m_currentMotionWasVisualLineUpOrDown = true; 0988 0989 return r; 0990 } 0991 0992 bool ModeBase::startNormalMode() 0993 { 0994 /* store the key presses for this "insert mode session" so that it can be repeated with the 0995 * '.' command 0996 * - ignore transition from Visual Modes 0997 */ 0998 if (!(m_viInputModeManager->isAnyVisualMode() || m_viInputModeManager->lastChangeRecorder()->isReplaying())) { 0999 m_viInputModeManager->storeLastChangeCommand(); 1000 m_viInputModeManager->clearCurrentChangeLog(); 1001 } 1002 1003 m_viInputModeManager->viEnterNormalMode(); 1004 m_view->doc()->setUndoMergeAllEdits(false); 1005 Q_EMIT m_view->viewModeChanged(m_view, m_view->viewMode()); 1006 1007 return true; 1008 } 1009 1010 bool ModeBase::startInsertMode() 1011 { 1012 m_viInputModeManager->viEnterInsertMode(); 1013 m_view->doc()->setUndoMergeAllEdits(true); 1014 Q_EMIT m_view->viewModeChanged(m_view, m_view->viewMode()); 1015 1016 return true; 1017 } 1018 1019 bool ModeBase::startReplaceMode() 1020 { 1021 m_view->doc()->setUndoMergeAllEdits(true); 1022 m_viInputModeManager->viEnterReplaceMode(); 1023 Q_EMIT m_view->viewModeChanged(m_view, m_view->viewMode()); 1024 1025 return true; 1026 } 1027 1028 bool ModeBase::startVisualMode() 1029 { 1030 if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualLineMode) { 1031 m_viInputModeManager->getViVisualMode()->setVisualModeType(ViMode::VisualMode); 1032 m_viInputModeManager->changeViMode(ViMode::VisualMode); 1033 } else if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualBlockMode) { 1034 m_viInputModeManager->getViVisualMode()->setVisualModeType(ViMode::VisualMode); 1035 m_viInputModeManager->changeViMode(ViMode::VisualMode); 1036 } else { 1037 m_viInputModeManager->viEnterVisualMode(); 1038 } 1039 1040 Q_EMIT m_view->viewModeChanged(m_view, m_view->viewMode()); 1041 1042 return true; 1043 } 1044 1045 bool ModeBase::startVisualBlockMode() 1046 { 1047 if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualMode) { 1048 m_viInputModeManager->getViVisualMode()->setVisualModeType(ViMode::VisualBlockMode); 1049 m_viInputModeManager->changeViMode(ViMode::VisualBlockMode); 1050 } else { 1051 m_viInputModeManager->viEnterVisualMode(ViMode::VisualBlockMode); 1052 } 1053 1054 Q_EMIT m_view->viewModeChanged(m_view, m_view->viewMode()); 1055 1056 return true; 1057 } 1058 1059 bool ModeBase::startVisualLineMode() 1060 { 1061 if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualMode) { 1062 m_viInputModeManager->getViVisualMode()->setVisualModeType(ViMode::VisualLineMode); 1063 m_viInputModeManager->changeViMode(ViMode::VisualLineMode); 1064 } else { 1065 m_viInputModeManager->viEnterVisualMode(ViMode::VisualLineMode); 1066 } 1067 1068 Q_EMIT m_view->viewModeChanged(m_view, m_view->viewMode()); 1069 1070 return true; 1071 } 1072 1073 void ModeBase::error(const QString &errorMsg) 1074 { 1075 delete m_infoMessage; 1076 1077 m_infoMessage = new KTextEditor::Message(errorMsg, KTextEditor::Message::Error); 1078 m_infoMessage->setPosition(KTextEditor::Message::BottomInView); 1079 m_infoMessage->setAutoHide(2000); // 2 seconds 1080 m_infoMessage->setView(m_view); 1081 1082 m_view->doc()->postMessage(m_infoMessage); 1083 } 1084 1085 void ModeBase::message(const QString &msg) 1086 { 1087 delete m_infoMessage; 1088 1089 m_infoMessage = new KTextEditor::Message(msg, KTextEditor::Message::Positive); 1090 m_infoMessage->setPosition(KTextEditor::Message::BottomInView); 1091 m_infoMessage->setAutoHide(2000); // 2 seconds 1092 m_infoMessage->setView(m_view); 1093 1094 m_view->doc()->postMessage(m_infoMessage); 1095 } 1096 1097 QString ModeBase::getVerbatimKeys() const 1098 { 1099 return m_keysVerbatim; 1100 } 1101 1102 const QChar ModeBase::getCharAtVirtualColumn(const QString &line, int virtualColumn, int tabWidth) 1103 { 1104 int column = 0; 1105 int tempCol = 0; 1106 1107 // sanity check: if the line is empty, there are no chars 1108 if (line.length() == 0) { 1109 return QChar::Null; 1110 } 1111 1112 while (tempCol < virtualColumn) { 1113 if (line.at(column) == QLatin1Char('\t')) { 1114 tempCol += tabWidth - (tempCol % tabWidth); 1115 } else { 1116 tempCol++; 1117 } 1118 1119 if (tempCol <= virtualColumn) { 1120 column++; 1121 1122 if (column >= line.length()) { 1123 return QChar::Null; 1124 } 1125 } 1126 } 1127 1128 if (line.length() > column) { 1129 return line.at(column); 1130 } 1131 1132 return QChar::Null; 1133 } 1134 1135 void ModeBase::addToNumberUnderCursor(int count) 1136 { 1137 KTextEditor::Cursor c(m_view->cursorPosition()); 1138 QString line = getLine(); 1139 1140 if (line.isEmpty()) { 1141 return; 1142 } 1143 1144 const int cursorColumn = c.column(); 1145 const int cursorLine = c.line(); 1146 const KTextEditor::Cursor prevWordStart = findPrevWordStart(cursorLine, cursorColumn); 1147 int wordStartPos = prevWordStart.column(); 1148 if (prevWordStart.line() < cursorLine) { 1149 // The previous word starts on the previous line: ignore. 1150 wordStartPos = 0; 1151 } 1152 if (wordStartPos > 0 && line.at(wordStartPos - 1) == QLatin1Char('-')) { 1153 wordStartPos--; 1154 } 1155 1156 int numberStartPos = -1; 1157 QString numberAsString; 1158 static const QRegularExpression numberRegex(QStringLiteral("0x[0-9a-fA-F]+|\\-?\\d+")); 1159 auto numberMatchIter = numberRegex.globalMatch(line, wordStartPos); 1160 while (numberMatchIter.hasNext()) { 1161 const auto numberMatch = numberMatchIter.next(); 1162 const bool numberEndedBeforeCursor = (numberMatch.capturedStart() + numberMatch.capturedLength() <= cursorColumn); 1163 if (!numberEndedBeforeCursor) { 1164 // This is the first number-like string under or after the cursor - this'll do! 1165 numberStartPos = numberMatch.capturedStart(); 1166 numberAsString = numberMatch.captured(); 1167 break; 1168 } 1169 } 1170 1171 if (numberStartPos == -1) { 1172 // None found. 1173 return; 1174 } 1175 1176 bool parsedNumberSuccessfully = false; 1177 int base = numberAsString.startsWith(QLatin1String("0x")) ? 16 : 10; 1178 if (base != 16 && numberAsString.startsWith(QLatin1Char('0')) && numberAsString.length() > 1) { 1179 // If a non-hex number with a leading 0 can be parsed as octal, then assume 1180 // it is octal. 1181 numberAsString.toInt(&parsedNumberSuccessfully, 8); 1182 if (parsedNumberSuccessfully) { 1183 base = 8; 1184 } 1185 } 1186 const int originalNumber = numberAsString.toInt(&parsedNumberSuccessfully, base); 1187 1188 if (!parsedNumberSuccessfully) { 1189 // conversion to int failed. give up. 1190 return; 1191 } 1192 1193 QString basePrefix; 1194 if (base == 16) { 1195 basePrefix = QStringLiteral("0x"); 1196 } else if (base == 8) { 1197 basePrefix = QStringLiteral("0"); 1198 } 1199 1200 const int withoutBaseLength = numberAsString.length() - basePrefix.length(); 1201 1202 const int newNumber = originalNumber + count; 1203 1204 // Create the new text string to be inserted. Prepend with “0x” if in base 16, and "0" if base 8. 1205 // For non-decimal numbers, try to keep the length of the number the same (including leading 0's). 1206 const QString newNumberPadded = 1207 (base == 10) ? QStringLiteral("%1").arg(newNumber, 0, base) : QStringLiteral("%1").arg(newNumber, withoutBaseLength, base, QLatin1Char('0')); 1208 const QString newNumberText = basePrefix + newNumberPadded; 1209 1210 // Replace the old number string with the new. 1211 doc()->editStart(); 1212 doc()->removeText(KTextEditor::Range(cursorLine, numberStartPos, cursorLine, numberStartPos + numberAsString.length())); 1213 doc()->insertText(KTextEditor::Cursor(cursorLine, numberStartPos), newNumberText); 1214 doc()->editEnd(); 1215 updateCursor(KTextEditor::Cursor(m_view->cursorPosition().line(), numberStartPos + newNumberText.length() - 1)); 1216 } 1217 1218 void ModeBase::switchView(Direction direction) 1219 { 1220 QList<KTextEditor::ViewPrivate *> visible_views; 1221 const auto views = KTextEditor::EditorPrivate::self()->views(); 1222 for (KTextEditor::ViewPrivate *view : views) { 1223 if (view->isVisible()) { 1224 visible_views.push_back(view); 1225 } 1226 } 1227 1228 QPoint current_point = m_view->mapToGlobal(m_view->pos()); 1229 int curr_x1 = current_point.x(); 1230 int curr_x2 = current_point.x() + m_view->width(); 1231 int curr_y1 = current_point.y(); 1232 int curr_y2 = current_point.y() + m_view->height(); 1233 const KTextEditor::Cursor cursorPos = m_view->cursorPosition(); 1234 const QPoint globalPos = m_view->mapToGlobal(m_view->cursorToCoordinate(cursorPos)); 1235 int curr_cursor_y = globalPos.y(); 1236 int curr_cursor_x = globalPos.x(); 1237 1238 KTextEditor::ViewPrivate *bestview = nullptr; 1239 int best_x1 = -1; 1240 int best_x2 = -1; 1241 int best_y1 = -1; 1242 int best_y2 = -1; 1243 int best_center_y = -1; 1244 int best_center_x = -1; 1245 1246 if (direction == Next && visible_views.count() != 1) { 1247 for (int i = 0; i < visible_views.count(); i++) { 1248 if (visible_views.at(i) == m_view) { 1249 if (i != visible_views.count() - 1) { 1250 bestview = visible_views.at(i + 1); 1251 } else { 1252 bestview = visible_views.at(0); 1253 } 1254 } 1255 } 1256 } else { 1257 for (KTextEditor::ViewPrivate *view : std::as_const(visible_views)) { 1258 QPoint point = view->mapToGlobal(view->pos()); 1259 int x1 = point.x(); 1260 int x2 = point.x() + view->width(); 1261 int y1 = point.y(); 1262 int y2 = point.y() + m_view->height(); 1263 int center_y = (y1 + y2) / 2; 1264 int center_x = (x1 + x2) / 2; 1265 1266 switch (direction) { 1267 case Left: 1268 if (view != m_view && x2 <= curr_x1 1269 && (x2 > best_x2 || (x2 == best_x2 && qAbs(curr_cursor_y - center_y) < qAbs(curr_cursor_y - best_center_y)) || bestview == nullptr)) { 1270 bestview = view; 1271 best_x2 = x2; 1272 best_center_y = center_y; 1273 } 1274 break; 1275 case Right: 1276 if (view != m_view && x1 >= curr_x2 1277 && (x1 < best_x1 || (x1 == best_x1 && qAbs(curr_cursor_y - center_y) < qAbs(curr_cursor_y - best_center_y)) || bestview == nullptr)) { 1278 bestview = view; 1279 best_x1 = x1; 1280 best_center_y = center_y; 1281 } 1282 break; 1283 case Down: 1284 if (view != m_view && y1 >= curr_y2 1285 && (y1 < best_y1 || (y1 == best_y1 && qAbs(curr_cursor_x - center_x) < qAbs(curr_cursor_x - best_center_x)) || bestview == nullptr)) { 1286 bestview = view; 1287 best_y1 = y1; 1288 best_center_x = center_x; 1289 } 1290 break; 1291 case Up: 1292 if (view != m_view && y2 <= curr_y1 1293 && (y2 > best_y2 || (y2 == best_y2 && qAbs(curr_cursor_x - center_x) < qAbs(curr_cursor_x - best_center_x)) || bestview == nullptr)) { 1294 bestview = view; 1295 best_y2 = y2; 1296 best_center_x = center_x; 1297 } 1298 break; 1299 default: 1300 return; 1301 } 1302 } 1303 } 1304 if (bestview != nullptr) { 1305 bestview->setFocus(); 1306 bestview->setInputMode(KTextEditor::View::ViInputMode); 1307 } 1308 } 1309 1310 Range ModeBase::motionFindPrev() 1311 { 1312 Searcher *searcher = m_viInputModeManager->searcher(); 1313 Range match = searcher->motionFindPrev(getCount()); 1314 if (searcher->lastSearchWrapped()) { 1315 m_view->showSearchWrappedHint(/*isReverseSearch*/ true); 1316 } 1317 1318 return match; 1319 } 1320 1321 Range ModeBase::motionFindNext() 1322 { 1323 Searcher *searcher = m_viInputModeManager->searcher(); 1324 Range match = searcher->motionFindNext(getCount()); 1325 if (searcher->lastSearchWrapped()) { 1326 m_view->showSearchWrappedHint(/*isReverseSearch*/ false); 1327 } 1328 1329 return match; 1330 } 1331 1332 void ModeBase::goToPos(const Range &r) 1333 { 1334 KTextEditor::Cursor c; 1335 c.setLine(r.endLine); 1336 c.setColumn(r.endColumn); 1337 1338 if (!c.isValid()) { 1339 return; 1340 } 1341 1342 if (r.jump) { 1343 m_viInputModeManager->jumps()->add(m_view->cursorPosition()); 1344 } 1345 1346 if (c.line() >= doc()->lines()) { 1347 c.setLine(doc()->lines() - 1); 1348 } 1349 1350 updateCursor(c); 1351 } 1352 1353 unsigned int ModeBase::linesDisplayed() const 1354 { 1355 return m_viInputModeManager->inputAdapter()->linesDisplayed(); 1356 } 1357 1358 void ModeBase::scrollViewLines(int l) 1359 { 1360 m_viInputModeManager->inputAdapter()->scrollViewLines(l); 1361 } 1362 1363 int ModeBase::getCount() const 1364 { 1365 if (m_oneTimeCountOverride != -1) { 1366 return m_oneTimeCountOverride; 1367 } 1368 return (m_count > 0) ? m_count : 1; 1369 } 1370 1371 #include "moc_modebase.cpp"