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 ®)
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 ®) 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 ®, 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