1 /*
2     SPDX-FileCopyrightText: 2008-2009 Erlend Hamberg <ehamberg@gmail.com>
3     SPDX-FileCopyrightText: 2009 Paul Gideon Dann <pdgiddie@gmail.com>
4     SPDX-FileCopyrightText: 2011 Svyatoslav Kuzmich <svatoslav1@gmail.com>
5     SPDX-FileCopyrightText: 2012-2013 Simon St James <kdedevel@etotheipiplusone.com>
6 
7     SPDX-License-Identifier: LGPL-2.0-or-later
8 */
9 
10 #include "kateconfig.h"
11 #include "katedocument.h"
12 #include "kateglobal.h"
13 #include "katelayoutcache.h"
14 #include "katerenderer.h"
15 #include "kateviewinternal.h"
16 #include "kateviinputmode.h"
17 #include <vimode/globalstate.h>
18 #include <vimode/inputmodemanager.h>
19 #include <vimode/jumps.h>
20 #include <vimode/lastchangerecorder.h>
21 #include <vimode/marks.h>
22 #include <vimode/modes/modebase.h>
23 #include <vimode/modes/normalvimode.h>
24 #include <vimode/modes/replacevimode.h>
25 #include <vimode/modes/visualvimode.h>
26 #include <vimode/registers.h>
27 #include <vimode/searcher.h>
28 
29 #include <KLocalizedString>
30 #include <QRegularExpression>
31 #include <QString>
32 
33 using namespace KateVi;
34 
35 // TODO: the "previous word/WORD [end]" methods should be optimized. now they're being called in a
36 // loop and all calculations done up to finding a match are trown away when called with a count > 1
37 // because they will simply be called again from the last found position.
38 // They should take the count as a parameter and collect the positions in a QList, then return
39 // element (count - 1)
40 
41 ////////////////////////////////////////////////////////////////////////////////
42 // HELPER METHODS
43 ////////////////////////////////////////////////////////////////////////////////
44 
yankToClipBoard(QChar chosen_register,const QString & text)45 void ModeBase::yankToClipBoard(QChar chosen_register, const QString &text)
46 {
47     // only yank to the clipboard if no register was specified,
48     // textlength > 1 and there is something else then whitespace
49     if ((chosen_register == QLatin1Char('0') || chosen_register == QLatin1Char('-') || chosen_register == PrependNumberedRegister) && text.length() > 1
50         && !text.trimmed().isEmpty()) {
51         KTextEditor::EditorPrivate::self()->copyToClipboard(text);
52     }
53 }
54 
deleteRange(Range & r,OperationMode mode,bool addToRegister)55 bool ModeBase::deleteRange(Range &r, OperationMode mode, bool addToRegister)
56 {
57     r.normalize();
58     bool res = false;
59     const QString removedText = getRange(r, mode);
60 
61     if (mode == LineWise) {
62         doc()->editStart();
63         for (int i = 0; i < r.endLine - r.startLine + 1; i++) {
64             res = doc()->removeLine(r.startLine);
65         }
66         doc()->editEnd();
67     } else {
68         res = doc()->removeText(r.toEditorRange(), mode == Block);
69     }
70 
71     // the BlackHoleRegister here is only a placeholder to signify that no register was selected
72     // this is needed because the fallback register depends on whether the deleted text spans a line/lines
73     QChar chosenRegister = getChosenRegister(BlackHoleRegister);
74     if (addToRegister) {
75         fillRegister(chosenRegister, removedText, mode);
76     }
77 
78     const QChar lastChar = removedText.count() > 0 ? removedText.back() : QLatin1Char('\0');
79     if (r.startLine != r.endLine || lastChar == QLatin1Char('\n') || lastChar == QLatin1Char('\r')) {
80         // for deletes spanning a line/lines, always prepend to the numbered registers
81         fillRegister(PrependNumberedRegister, removedText, mode);
82         chosenRegister = PrependNumberedRegister;
83     } else if (chosenRegister == BlackHoleRegister) {
84         // only set the SmallDeleteRegister when no register was selected
85         fillRegister(SmallDeleteRegister, removedText, mode);
86         chosenRegister = SmallDeleteRegister;
87     }
88     yankToClipBoard(chosenRegister, removedText);
89 
90     return res;
91 }
92 
getRange(Range & r,OperationMode mode) const93 const QString ModeBase::getRange(Range &r, OperationMode mode) const
94 {
95     r.normalize();
96     QString s;
97 
98     if (mode == LineWise) {
99         r.startColumn = 0;
100         r.endColumn = getLine(r.endLine).length();
101     }
102 
103     if (r.motionType == InclusiveMotion) {
104         r.endColumn++;
105     }
106 
107     KTextEditor::Range range = r.toEditorRange();
108     if (mode == LineWise) {
109         s = doc()->textLines(range).join(QLatin1Char('\n'));
110         s.append(QLatin1Char('\n'));
111     } else if (mode == Block) {
112         s = doc()->text(range, true);
113     } else {
114         s = doc()->text(range);
115     }
116 
117     return s;
118 }
119 
getLine(int line) const120 const QString ModeBase::getLine(int line) const
121 {
122     return (line < 0) ? m_view->currentTextLine() : doc()->line(line);
123 }
124 
getCharUnderCursor() const125 const QChar ModeBase::getCharUnderCursor() const
126 {
127     KTextEditor::Cursor c(m_view->cursorPosition());
128 
129     QString line = getLine(c.line());
130 
131     if (line.length() == 0 && c.column() >= line.length()) {
132         return QChar::Null;
133     }
134 
135     return line.at(c.column());
136 }
137 
getWordUnderCursor() const138 const QString ModeBase::getWordUnderCursor() const
139 {
140     return doc()->text(getWordRangeUnderCursor());
141 }
142 
getWordRangeUnderCursor() const143 const KTextEditor::Range ModeBase::getWordRangeUnderCursor() const
144 {
145     KTextEditor::Cursor c(m_view->cursorPosition());
146 
147     // find first character that is a “word letter” and start the search there
148     QChar ch = doc()->characterAt(c);
149     int i = 0;
150     while (!ch.isLetterOrNumber() && !ch.isMark() && ch != QLatin1Char('_') && m_extraWordCharacters.indexOf(ch) == -1) {
151         // advance cursor one position
152         c.setColumn(c.column() + 1);
153         if (c.column() > doc()->lineLength(c.line())) {
154             c.setColumn(0);
155             c.setLine(c.line() + 1);
156             if (c.line() == doc()->lines()) {
157                 return KTextEditor::Range::invalid();
158             }
159         }
160 
161         ch = doc()->characterAt(c);
162         i++; // count characters that were advanced so we know where to start the search
163     }
164 
165     // move cursor the word (if cursor was placed on e.g. a paren, this will move
166     // it to the right
167     updateCursor(c);
168 
169     KTextEditor::Cursor c1 = findPrevWordStart(c.line(), c.column() + 1 + i, true);
170     KTextEditor::Cursor c2 = findWordEnd(c1.line(), c1.column() + i - 1, true);
171     c2.setColumn(c2.column() + 1);
172 
173     return KTextEditor::Range(c1, c2);
174 }
175 
findNextWordStart(int fromLine,int fromColumn,bool onlyCurrentLine) const176 KTextEditor::Cursor ModeBase::findNextWordStart(int fromLine, int fromColumn, bool onlyCurrentLine) const
177 {
178     QString line = getLine(fromLine);
179 
180     // the start of word pattern need to take m_extraWordCharacters into account if defined
181     QString startOfWordPattern = QStringLiteral("\\b(\\w");
182     if (m_extraWordCharacters.length() > 0) {
183         startOfWordPattern.append(QLatin1String("|[") + m_extraWordCharacters + QLatin1Char(']'));
184     }
185     startOfWordPattern.append(QLatin1Char(')'));
186 
187     const QRegularExpression startOfWord(startOfWordPattern, QRegularExpression::UseUnicodePropertiesOption); // start of a word
188     static const QRegularExpression nonSpaceAfterSpace(QStringLiteral("\\s\\S"), QRegularExpression::UseUnicodePropertiesOption); // non-space right after space
189     static const QRegularExpression nonWordAfterWord(QStringLiteral("\\b(?!\\s)\\W"), QRegularExpression::UseUnicodePropertiesOption); // word-boundary followed by a non-word which is not a space
190 
191     int l = fromLine;
192     int c = fromColumn;
193 
194     bool found = false;
195 
196     while (!found) {
197         int c1 = line.indexOf(startOfWord, c + 1);
198         int c2 = line.indexOf(nonSpaceAfterSpace, c);
199         int c3 = line.indexOf(nonWordAfterWord, c + 1);
200 
201         if (c1 == -1 && c2 == -1 && c3 == -1) {
202             if (onlyCurrentLine) {
203                 return KTextEditor::Cursor::invalid();
204             } else if (l >= doc()->lines() - 1) {
205                 c = qMax(line.length() - 1, 0);
206                 return KTextEditor::Cursor::invalid();
207             } else {
208                 c = 0;
209                 l++;
210 
211                 line = getLine(l);
212 
213                 if (line.length() == 0 || !line.at(c).isSpace()) {
214                     found = true;
215                 }
216 
217                 continue;
218             }
219         }
220 
221         c2++; // the second regexp will match one character *before* the character we want to go to
222 
223         if (c1 <= 0) {
224             c1 = line.length() - 1;
225         }
226         if (c2 <= 0) {
227             c2 = line.length() - 1;
228         }
229         if (c3 <= 0) {
230             c3 = line.length() - 1;
231         }
232 
233         c = qMin(c1, qMin(c2, c3));
234 
235         found = true;
236     }
237 
238     return KTextEditor::Cursor(l, c);
239 }
240 
findNextWORDStart(int fromLine,int fromColumn,bool onlyCurrentLine) const241 KTextEditor::Cursor ModeBase::findNextWORDStart(int fromLine, int fromColumn, bool onlyCurrentLine) const
242 {
243     QString line = getLine();
244 
245     int l = fromLine;
246     int c = fromColumn;
247 
248     bool found = false;
249     static const QRegularExpression startOfWORD(QStringLiteral("\\s\\S"), QRegularExpression::UseUnicodePropertiesOption);
250 
251     while (!found) {
252         c = line.indexOf(startOfWORD, c);
253 
254         if (c == -1) {
255             if (onlyCurrentLine) {
256                 return KTextEditor::Cursor(l, c);
257             } else if (l >= doc()->lines() - 1) {
258                 c = line.length() - 1;
259                 break;
260             } else {
261                 c = 0;
262                 l++;
263 
264                 line = getLine(l);
265 
266                 if (line.length() == 0 || !line.at(c).isSpace()) {
267                     found = true;
268                 }
269 
270                 continue;
271             }
272         } else {
273             c++;
274             found = true;
275         }
276     }
277 
278     return KTextEditor::Cursor(l, c);
279 }
280 
findPrevWordEnd(int fromLine,int fromColumn,bool onlyCurrentLine) const281 KTextEditor::Cursor ModeBase::findPrevWordEnd(int fromLine, int fromColumn, bool onlyCurrentLine) const
282 {
283     QString line = getLine(fromLine);
284 
285     QString endOfWordPattern = QStringLiteral("\\S\\s|\\S$|\\S\\b|\\w\\W|^$");
286 
287     if (m_extraWordCharacters.length() > 0) {
288         endOfWordPattern.append(QLatin1String("|[") + m_extraWordCharacters + QLatin1String("][^") + m_extraWordCharacters + QLatin1Char(']'));
289     }
290 
291     const QRegularExpression endOfWord(endOfWordPattern);
292 
293     int l = fromLine;
294     int c = fromColumn;
295 
296     bool found = false;
297 
298     while (!found) {
299         int c1 = line.lastIndexOf(endOfWord, c - 1);
300 
301         if (c1 != -1 && c - 1 != -1) {
302             found = true;
303             c = c1;
304         } else {
305             if (onlyCurrentLine) {
306                 return KTextEditor::Cursor::invalid();
307             } else if (l > 0) {
308                 line = getLine(--l);
309                 c = line.length();
310 
311                 continue;
312             } else {
313                 return KTextEditor::Cursor::invalid();
314             }
315         }
316     }
317 
318     return KTextEditor::Cursor(l, c);
319 }
320 
findPrevWORDEnd(int fromLine,int fromColumn,bool onlyCurrentLine) const321 KTextEditor::Cursor ModeBase::findPrevWORDEnd(int fromLine, int fromColumn, bool onlyCurrentLine) const
322 {
323     QString line = getLine(fromLine);
324 
325     static const QRegularExpression endOfWORDPattern(QStringLiteral("\\S\\s|\\S$|^$"), QRegularExpression::UseUnicodePropertiesOption);
326 
327     int l = fromLine;
328     int c = fromColumn;
329 
330     bool found = false;
331 
332     while (!found) {
333         int c1 = line.lastIndexOf(endOfWORDPattern, c - 1);
334 
335         if (c1 != -1 && c - 1 != -1) {
336             found = true;
337             c = c1;
338         } else {
339             if (onlyCurrentLine) {
340                 return KTextEditor::Cursor::invalid();
341             } else if (l > 0) {
342                 line = getLine(--l);
343                 c = line.length();
344 
345                 continue;
346             } else {
347                 c = 0;
348                 return KTextEditor::Cursor::invalid();
349             }
350         }
351     }
352 
353     return KTextEditor::Cursor(l, c);
354 }
355 
findPrevWordStart(int fromLine,int fromColumn,bool onlyCurrentLine) const356 KTextEditor::Cursor ModeBase::findPrevWordStart(int fromLine, int fromColumn, bool onlyCurrentLine) const
357 {
358     QString line = getLine(fromLine);
359 
360     // the start of word pattern need to take m_extraWordCharacters into account if defined
361     QString startOfWordPattern = QStringLiteral("\\b(\\w");
362     if (m_extraWordCharacters.length() > 0) {
363         startOfWordPattern.append(QLatin1String("|[") + m_extraWordCharacters + QLatin1Char(']'));
364     }
365     startOfWordPattern.append(QLatin1Char(')'));
366 
367     const QRegularExpression startOfWord(startOfWordPattern, QRegularExpression::UseUnicodePropertiesOption); // start of a word
368     static const QRegularExpression nonSpaceAfterSpace(QStringLiteral("\\s\\S"), QRegularExpression::UseUnicodePropertiesOption); // non-space right after space
369     static const QRegularExpression nonWordAfterWord(QStringLiteral("\\b(?!\\s)\\W"), QRegularExpression::UseUnicodePropertiesOption); // word-boundary followed by a non-word which is not a space
370     static const QRegularExpression startOfLine(QStringLiteral("^\\S"), QRegularExpression::UseUnicodePropertiesOption); // non-space at start of line
371 
372     int l = fromLine;
373     int c = fromColumn;
374 
375     bool found = false;
376 
377     while (!found) {
378         int c1 = (c > 0) ? line.lastIndexOf(startOfWord, c - 1) : -1;
379         int c2 = (c > 1) ? line.lastIndexOf(nonSpaceAfterSpace, c - 2) : -1;
380         int c3 = (c > 0) ? line.lastIndexOf(nonWordAfterWord, c - 1) : -1;
381         int c4 = (c > 0) ? line.lastIndexOf(startOfLine, c - 1) : -1;
382 
383         if (c1 == -1 && c2 == -1 && c3 == -1 && c4 == -1) {
384             if (onlyCurrentLine) {
385                 return KTextEditor::Cursor::invalid();
386             } else if (l <= 0) {
387                 return KTextEditor::Cursor::invalid();
388             } else {
389                 line = getLine(--l);
390                 c = line.length();
391 
392                 if (line.length() == 0) {
393                     c = 0;
394                     found = true;
395                 }
396 
397                 continue;
398             }
399         }
400 
401         c2++; // the second regexp will match one character *before* the character we want to go to
402 
403         if (c1 <= 0) {
404             c1 = 0;
405         }
406         if (c2 <= 0) {
407             c2 = 0;
408         }
409         if (c3 <= 0) {
410             c3 = 0;
411         }
412         if (c4 <= 0) {
413             c4 = 0;
414         }
415 
416         c = qMax(c1, qMax(c2, qMax(c3, c4)));
417 
418         found = true;
419     }
420 
421     return KTextEditor::Cursor(l, c);
422 }
423 
findPrevWORDStart(int fromLine,int fromColumn,bool onlyCurrentLine) const424 KTextEditor::Cursor ModeBase::findPrevWORDStart(int fromLine, int fromColumn, bool onlyCurrentLine) const
425 {
426     QString line = getLine(fromLine);
427 
428     static const QRegularExpression startOfWORD(QStringLiteral("\\s\\S"), QRegularExpression::UseUnicodePropertiesOption);
429     static const QRegularExpression startOfLineWORD(QStringLiteral("^\\S"), QRegularExpression::UseUnicodePropertiesOption);
430 
431     int l = fromLine;
432     int c = fromColumn;
433 
434     bool found = false;
435 
436     while (!found) {
437         int c1 = (c > 1) ? line.lastIndexOf(startOfWORD, c - 2) : -1;
438         int c2 = (c > 0) ? line.lastIndexOf(startOfLineWORD, c - 1) : -1;
439 
440         if (c1 == -1 && c2 == -1) {
441             if (onlyCurrentLine) {
442                 return KTextEditor::Cursor::invalid();
443             } else if (l <= 0) {
444                 return KTextEditor::Cursor::invalid();
445             } else {
446                 line = getLine(--l);
447                 c = line.length();
448 
449                 if (line.length() == 0) {
450                     c = 0;
451                     found = true;
452                 }
453 
454                 continue;
455             }
456         }
457 
458         c1++; // the startOfWORD pattern matches one character before the word
459 
460         c = qMax(c1, c2);
461 
462         if (c <= 0) {
463             c = 0;
464         }
465 
466         found = true;
467     }
468 
469     return KTextEditor::Cursor(l, c);
470 }
471 
findWordEnd(int fromLine,int fromColumn,bool onlyCurrentLine) const472 KTextEditor::Cursor ModeBase::findWordEnd(int fromLine, int fromColumn, bool onlyCurrentLine) const
473 {
474     QString line = getLine(fromLine);
475 
476     QString endOfWordPattern = QStringLiteral("\\S\\s|\\S$|\\w\\W|\\S\\b");
477 
478     if (m_extraWordCharacters.length() > 0) {
479         endOfWordPattern.append(QLatin1String("|[") + m_extraWordCharacters + QLatin1String("][^") + m_extraWordCharacters + QLatin1Char(']'));
480     }
481 
482     const QRegularExpression endOfWORD(endOfWordPattern);
483 
484     int l = fromLine;
485     int c = fromColumn;
486 
487     bool found = false;
488 
489     while (!found) {
490         int c1 = line.indexOf(endOfWORD, c + 1);
491 
492         if (c1 != -1) {
493             found = true;
494             c = c1;
495         } else {
496             if (onlyCurrentLine) {
497                 return KTextEditor::Cursor::invalid();
498             } else if (l >= doc()->lines() - 1) {
499                 c = line.length() - 1;
500                 return KTextEditor::Cursor::invalid();
501             } else {
502                 c = -1;
503                 line = getLine(++l);
504 
505                 continue;
506             }
507         }
508     }
509 
510     return KTextEditor::Cursor(l, c);
511 }
512 
findWORDEnd(int fromLine,int fromColumn,bool onlyCurrentLine) const513 KTextEditor::Cursor ModeBase::findWORDEnd(int fromLine, int fromColumn, bool onlyCurrentLine) const
514 {
515     QString line = getLine(fromLine);
516 
517     static const QRegularExpression endOfWORD(QStringLiteral("\\S\\s|\\S$"), QRegularExpression::UseUnicodePropertiesOption);
518 
519     int l = fromLine;
520     int c = fromColumn;
521 
522     bool found = false;
523 
524     while (!found) {
525         int c1 = line.indexOf(endOfWORD, c + 1);
526 
527         if (c1 != -1) {
528             found = true;
529             c = c1;
530         } else {
531             if (onlyCurrentLine) {
532                 return KTextEditor::Cursor::invalid();
533             } else if (l >= doc()->lines() - 1) {
534                 c = line.length() - 1;
535                 return KTextEditor::Cursor::invalid();
536             } else {
537                 c = -1;
538                 line = getLine(++l);
539 
540                 continue;
541             }
542         }
543     }
544 
545     return KTextEditor::Cursor(l, c);
546 }
547 
innerRange(Range range,bool inner)548 Range innerRange(Range range, bool inner)
549 {
550     Range r = range;
551 
552     if (inner) {
553         const int columnDistance = qAbs(r.startColumn - r.endColumn);
554         if ((r.startLine == r.endLine) && columnDistance == 1) {
555             // Start and end are right next to each other; there is nothing inside them.
556             return Range::invalid();
557         }
558         r.startColumn++;
559         r.endColumn--;
560     }
561 
562     return r;
563 }
564 
findSurroundingQuotes(const QChar & c,bool inner) const565 Range ModeBase::findSurroundingQuotes(const QChar &c, bool inner) const
566 {
567     KTextEditor::Cursor cursor(m_view->cursorPosition());
568     Range r;
569     r.startLine = cursor.line();
570     r.endLine = cursor.line();
571 
572     QString line = doc()->line(cursor.line());
573 
574     // If cursor on the quote we should choose the best direction.
575     if (line.at(cursor.column()) == c) {
576         int attribute = m_view->doc()->kateTextLine(cursor.line())->attribute(cursor.column());
577 
578         //  If at the beginning of the line - then we might search the end.
579         if (doc()->kateTextLine(cursor.line())->attribute(cursor.column() + 1) == attribute
580             && doc()->kateTextLine(cursor.line())->attribute(cursor.column() - 1) != attribute) {
581             r.startColumn = cursor.column();
582             r.endColumn = line.indexOf(c, cursor.column() + 1);
583 
584             return innerRange(r, inner);
585         }
586 
587         //  If at the end of the line - then we might search the beginning.
588         if (doc()->kateTextLine(cursor.line())->attribute(cursor.column() + 1) != attribute
589             && doc()->kateTextLine(cursor.line())->attribute(cursor.column() - 1) == attribute) {
590             r.startColumn = line.lastIndexOf(c, cursor.column() - 1);
591             r.endColumn = cursor.column();
592 
593             return innerRange(r, inner);
594         }
595         // Try to search the quote to right
596         int c1 = line.indexOf(c, cursor.column() + 1);
597         if (c1 != -1) {
598             r.startColumn = cursor.column();
599             r.endColumn = c1;
600 
601             return innerRange(r, inner);
602         }
603 
604         // Try to search the quote to left
605         int c2 = line.lastIndexOf(c, cursor.column() - 1);
606         if (c2 != -1) {
607             r.startColumn = c2;
608             r.endColumn = cursor.column();
609 
610             return innerRange(r, inner);
611         }
612 
613         // Nothing found - give up :)
614         return Range::invalid();
615     }
616 
617     r.startColumn = line.lastIndexOf(c, cursor.column());
618     r.endColumn = line.indexOf(c, cursor.column());
619 
620     if (r.startColumn == -1 || r.endColumn == -1 || r.startColumn > r.endColumn) {
621         return Range::invalid();
622     }
623 
624     return innerRange(r, inner);
625 }
626 
findSurroundingBrackets(const QChar & c1,const QChar & c2,bool inner,const QChar & nested1,const QChar & nested2) const627 Range ModeBase::findSurroundingBrackets(const QChar &c1, const QChar &c2, bool inner, const QChar &nested1, const QChar &nested2) const
628 {
629     KTextEditor::Cursor cursor(m_view->cursorPosition());
630     Range r(cursor, InclusiveMotion);
631     int line = cursor.line();
632     int column = cursor.column();
633     int catalan;
634 
635     // Chars should not differ. For equal chars use findSurroundingQuotes.
636     Q_ASSERT(c1 != c2);
637 
638     const QString &l = m_view->doc()->line(line);
639     if (column < l.size() && l.at(column) == c2) {
640         r.endLine = line;
641         r.endColumn = column;
642     } else {
643         if (column < l.size() && l.at(column) == c1) {
644             column++;
645         }
646 
647         for (catalan = 1; line < m_view->doc()->lines(); line++) {
648             const QString &l = m_view->doc()->line(line);
649 
650             for (; column < l.size(); column++) {
651                 const QChar &c = l.at(column);
652 
653                 if (c == nested1) {
654                     catalan++;
655                 } else if (c == nested2) {
656                     catalan--;
657                 }
658                 if (!catalan) {
659                     break;
660                 }
661             }
662             if (!catalan) {
663                 break;
664             }
665             column = 0;
666         }
667 
668         if (catalan != 0) {
669             return Range::invalid();
670         }
671         r.endLine = line;
672         r.endColumn = column;
673     }
674 
675     // Same algorithm but backwards.
676     line = cursor.line();
677     column = cursor.column();
678 
679     if (column < l.size() && l.at(column) == c1) {
680         r.startLine = line;
681         r.startColumn = column;
682     } else {
683         if (column < l.size() && l.at(column) == c2) {
684             column--;
685         }
686 
687         for (catalan = 1; line >= 0; line--) {
688             const QString &l = m_view->doc()->line(line);
689 
690             for (; column >= 0; column--) {
691                 const QChar &c = l.at(column);
692 
693                 if (c == nested1) {
694                     catalan--;
695                 } else if (c == nested2) {
696                     catalan++;
697                 }
698                 if (!catalan) {
699                     break;
700                 }
701             }
702             if (!catalan || !line) {
703                 break;
704             }
705             column = m_view->doc()->line(line - 1).size() - 1;
706         }
707         if (catalan != 0) {
708             return Range::invalid();
709         }
710         r.startColumn = column;
711         r.startLine = line;
712     }
713 
714     return innerRange(r, inner);
715 }
716 
findSurrounding(const QRegularExpression & c1,const QRegularExpression & c2,bool inner) const717 Range ModeBase::findSurrounding(const QRegularExpression &c1, const QRegularExpression &c2, bool inner) const
718 {
719     KTextEditor::Cursor cursor(m_view->cursorPosition());
720     QString line = getLine();
721 
722     int col1 = line.lastIndexOf(c1, cursor.column());
723     int col2 = line.indexOf(c2, cursor.column());
724 
725     Range r(cursor.line(), col1, cursor.line(), col2, InclusiveMotion);
726 
727     if (col1 == -1 || col2 == -1 || col1 > col2) {
728         return Range::invalid();
729     }
730 
731     if (inner) {
732         r.startColumn++;
733         r.endColumn--;
734     }
735 
736     return r;
737 }
738 
findLineStartingWitchChar(const QChar & c,int count,bool forward) const739 int ModeBase::findLineStartingWitchChar(const QChar &c, int count, bool forward) const
740 {
741     int line = m_view->cursorPosition().line();
742     int lines = doc()->lines();
743     int hits = 0;
744 
745     if (forward) {
746         line++;
747     } else {
748         line--;
749     }
750 
751     while (line < lines && line >= 0 && hits < count) {
752         QString l = getLine(line);
753         if (l.length() > 0 && l.at(0) == c) {
754             hits++;
755         }
756         if (hits != count) {
757             if (forward) {
758                 line++;
759             } else {
760                 line--;
761             }
762         }
763     }
764 
765     if (hits == getCount()) {
766         return line;
767     }
768 
769     return -1;
770 }
771 
updateCursor(const KTextEditor::Cursor c) const772 void ModeBase::updateCursor(const KTextEditor::Cursor c) const
773 {
774     m_viInputModeManager->updateCursor(c);
775 }
776 
777 /**
778  * @return the register given for the command. If no register was given, defaultReg is returned.
779  */
getChosenRegister(const QChar & defaultReg) const780 QChar ModeBase::getChosenRegister(const QChar &defaultReg) const
781 {
782     return (m_register != QChar::Null) ? m_register : defaultReg;
783 }
784 
getRegisterContent(const QChar & reg)785 QString ModeBase::getRegisterContent(const QChar &reg)
786 {
787     QString r = m_viInputModeManager->globalState()->registers()->getContent(reg);
788 
789     if (r.isNull()) {
790         error(i18n("Nothing in register %1", reg.toLower()));
791     }
792 
793     return r;
794 }
795 
getRegisterFlag(const QChar & reg) const796 OperationMode ModeBase::getRegisterFlag(const QChar &reg) const
797 {
798     return m_viInputModeManager->globalState()->registers()->getFlag(reg);
799 }
800 
fillRegister(const QChar & reg,const QString & text,OperationMode flag)801 void ModeBase::fillRegister(const QChar &reg, const QString &text, OperationMode flag)
802 {
803     m_viInputModeManager->globalState()->registers()->set(reg, text, flag);
804 }
805 
getNextJump(KTextEditor::Cursor cursor) const806 KTextEditor::Cursor ModeBase::getNextJump(KTextEditor::Cursor cursor) const
807 {
808     return m_viInputModeManager->jumps()->next(cursor);
809 }
810 
getPrevJump(KTextEditor::Cursor cursor) const811 KTextEditor::Cursor ModeBase::getPrevJump(KTextEditor::Cursor cursor) const
812 {
813     return m_viInputModeManager->jumps()->prev(cursor);
814 }
815 
goLineDown()816 Range ModeBase::goLineDown()
817 {
818     return goLineUpDown(getCount());
819 }
820 
goLineUp()821 Range ModeBase::goLineUp()
822 {
823     return goLineUpDown(-getCount());
824 }
825 
826 /**
827  * method for moving up or down one or more lines
828  * note: the sticky column is always a virtual column
829  */
goLineUpDown(int lines)830 Range ModeBase::goLineUpDown(int lines)
831 {
832     KTextEditor::Cursor c(m_view->cursorPosition());
833     Range r(c, InclusiveMotion);
834     int tabstop = doc()->config()->tabWidth();
835 
836     // if in an empty document, just return
837     if (lines == 0) {
838         return r;
839     }
840 
841     r.endLine += lines;
842 
843     // limit end line to be from line 0 through the last line
844     if (r.endLine < 0) {
845         r.endLine = 0;
846     } else if (r.endLine > doc()->lines() - 1) {
847         r.endLine = doc()->lines() - 1;
848     }
849 
850     Kate::TextLine startLine = doc()->plainKateTextLine(c.line());
851     Kate::TextLine endLine = doc()->plainKateTextLine(r.endLine);
852 
853     int endLineLen = doc()->lineLength(r.endLine) - 1;
854 
855     if (endLineLen < 0) {
856         endLineLen = 0;
857     }
858 
859     int endLineLenVirt = endLine->toVirtualColumn(endLineLen, tabstop);
860     int virtColumnStart = startLine->toVirtualColumn(c.column(), tabstop);
861 
862     // if sticky column isn't set, set end column and set sticky column to its virtual column
863     if (m_stickyColumn == -1) {
864         r.endColumn = endLine->fromVirtualColumn(virtColumnStart, tabstop);
865         m_stickyColumn = virtColumnStart;
866     } else {
867         // sticky is set - set end column to its value
868         r.endColumn = endLine->fromVirtualColumn(m_stickyColumn, tabstop);
869     }
870 
871     // make sure end column won't be after the last column of a line
872     if (r.endColumn > endLineLen) {
873         r.endColumn = endLineLen;
874     }
875 
876     // if we move to a line shorter than the current column, go to its end
877     if (virtColumnStart > endLineLenVirt) {
878         r.endColumn = endLineLen;
879     }
880 
881     return r;
882 }
883 
goVisualLineUpDown(int lines)884 Range ModeBase::goVisualLineUpDown(int lines)
885 {
886     KTextEditor::Cursor c(m_view->cursorPosition());
887     Range r(c, InclusiveMotion);
888     int tabstop = doc()->config()->tabWidth();
889 
890     if (lines == 0) {
891         // We're not moving anywhere.
892         return r;
893     }
894 
895     KateLayoutCache *cache = m_viInputModeManager->inputAdapter()->layoutCache();
896 
897     // Work out the real and visual line pair of the beginning of the visual line we'd end up
898     // on by moving lines visual lines.  We ignore the column, for now.
899     int finishVisualLine = cache->viewLine(m_view->cursorPosition());
900     int finishRealLine = m_view->cursorPosition().line();
901     int count = qAbs(lines);
902     bool invalidPos = false;
903     if (lines > 0) {
904         // Find the beginning of the visual line "lines" visual lines down.
905         while (count > 0) {
906             finishVisualLine++;
907             if (finishVisualLine >= cache->line(finishRealLine)->viewLineCount()) {
908                 finishRealLine++;
909                 finishVisualLine = 0;
910             }
911             if (finishRealLine >= doc()->lines()) {
912                 invalidPos = true;
913                 break;
914             }
915             count--;
916         }
917     } else {
918         // Find the beginning of the visual line "lines" visual lines up.
919         while (count > 0) {
920             finishVisualLine--;
921             if (finishVisualLine < 0) {
922                 finishRealLine--;
923                 if (finishRealLine < 0) {
924                     invalidPos = true;
925                     break;
926                 }
927                 finishVisualLine = cache->line(finishRealLine)->viewLineCount() - 1;
928             }
929             count--;
930         }
931     }
932     if (invalidPos) {
933         r.endLine = -1;
934         r.endColumn = -1;
935         return r;
936     }
937 
938     // We know the final (real) line ...
939     r.endLine = finishRealLine;
940     // ... now work out the final (real) column.
941 
942     if (m_stickyColumn == -1 || !m_lastMotionWasVisualLineUpOrDown) {
943         // Compute new sticky column. It is a *visual* sticky column.
944         int startVisualLine = cache->viewLine(m_view->cursorPosition());
945         int startRealLine = m_view->cursorPosition().line();
946         const Kate::TextLine startLine = doc()->plainKateTextLine(c.line());
947         // Adjust for the fact that if the portion of the line before wrapping is indented,
948         // the continuations are also "invisibly" (i.e. without any spaces in the text itself) indented.
949         const bool isWrappedContinuation = (cache->textLayout(startRealLine, startVisualLine).lineLayout().lineNumber() != 0);
950         const int numInvisibleIndentChars =
951             isWrappedContinuation ? startLine->toVirtualColumn(cache->line(startRealLine)->textLine()->nextNonSpaceChar(0), tabstop) : 0;
952 
953         const int realLineStartColumn = cache->textLayout(startRealLine, startVisualLine).startCol();
954         const int lineStartVirtualColumn = startLine->toVirtualColumn(realLineStartColumn, tabstop);
955         const int visualColumnNoInvisibleIndent = startLine->toVirtualColumn(c.column(), tabstop) - lineStartVirtualColumn;
956         m_stickyColumn = visualColumnNoInvisibleIndent + numInvisibleIndentChars;
957         Q_ASSERT(m_stickyColumn >= 0);
958     }
959 
960     // The "real" (non-virtual) beginning of the current "line", which might be a wrapped continuation of a
961     // "real" line.
962     const int realLineStartColumn = cache->textLayout(finishRealLine, finishVisualLine).startCol();
963     const Kate::TextLine endLine = doc()->plainKateTextLine(r.endLine);
964     // Adjust for the fact that if the portion of the line before wrapping is indented,
965     // the continuations are also "invisibly" (i.e. without any spaces in the text itself) indented.
966     const bool isWrappedContinuation = (cache->textLayout(finishRealLine, finishVisualLine).lineLayout().lineNumber() != 0);
967     const int numInvisibleIndentChars =
968         isWrappedContinuation ? endLine->toVirtualColumn(cache->line(finishRealLine)->textLine()->nextNonSpaceChar(0), tabstop) : 0;
969     if (m_stickyColumn == (unsigned int)KateVi::EOL) {
970         const int visualEndColumn = cache->textLayout(finishRealLine, finishVisualLine).lineLayout().textLength() - 1;
971         r.endColumn = endLine->fromVirtualColumn(visualEndColumn + realLineStartColumn - numInvisibleIndentChars, tabstop);
972     } else {
973         // Algorithm: find the "real" column corresponding to the start of the line.  Offset from that
974         // until the "visual" column is equal to the "visual" sticky column.
975         int realOffsetToVisualStickyColumn = 0;
976         const int lineStartVirtualColumn = endLine->toVirtualColumn(realLineStartColumn, tabstop);
977         while (true) {
978             const int visualColumn =
979                 endLine->toVirtualColumn(realLineStartColumn + realOffsetToVisualStickyColumn, tabstop) - lineStartVirtualColumn + numInvisibleIndentChars;
980             if (visualColumn >= m_stickyColumn) {
981                 break;
982             }
983             realOffsetToVisualStickyColumn++;
984         }
985         r.endColumn = realLineStartColumn + realOffsetToVisualStickyColumn;
986     }
987     m_currentMotionWasVisualLineUpOrDown = true;
988 
989     return r;
990 }
991 
startNormalMode()992 bool ModeBase::startNormalMode()
993 {
994     /* store the key presses for this "insert mode session" so that it can be repeated with the
995      * '.' command
996      * - ignore transition from Visual Modes
997      */
998     if (!(m_viInputModeManager->isAnyVisualMode() || m_viInputModeManager->lastChangeRecorder()->isReplaying())) {
999         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 
startInsertMode()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 
startReplaceMode()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 
startVisualMode()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 
startVisualBlockMode()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 
startVisualLineMode()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 
error(const QString & errorMsg)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 
message(const QString & msg)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 
getVerbatimKeys() const1097 QString ModeBase::getVerbatimKeys() const
1098 {
1099     return m_keysVerbatim;
1100 }
1101 
getCharAtVirtualColumn(const QString & line,int virtualColumn,int tabWidth) const1102 const QChar ModeBase::getCharAtVirtualColumn(const QString &line, int virtualColumn, int tabWidth) const
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 
addToNumberUnderCursor(int count)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 
switchView(Direction direction)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 
motionFindPrev()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 
motionFindNext()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 
goToPos(const Range & r)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 
linesDisplayed() const1353 unsigned int ModeBase::linesDisplayed() const
1354 {
1355     return m_viInputModeManager->inputAdapter()->linesDisplayed();
1356 }
1357 
scrollViewLines(int l)1358 void ModeBase::scrollViewLines(int l)
1359 {
1360     m_viInputModeManager->inputAdapter()->scrollViewLines(l);
1361 }
1362 
getCount() const1363 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