File indexing completed on 2024-05-12 11:58:32

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