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