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 &reg)
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 &reg) const
0800 {
0801     return m_viInputModeManager->globalState()->registers()->getFlag(reg);
0802 }
0803 
0804 void ModeBase::fillRegister(const QChar &reg, 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 }