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 "kateviinputmode.h"
15#include "ktexteditor/message.h"
16#include <vimode/globalstate.h>
17#include <vimode/inputmodemanager.h>
18#include <vimode/jumps.h>
19#include <vimode/lastchangerecorder.h>
20#include <vimode/marks.h>
21#include <vimode/modes/modebase.h>
22#include <vimode/modes/normalvimode.h>
23#include <vimode/modes/replacevimode.h>
24#include <vimode/modes/visualvimode.h>
25#include <vimode/registers.h>
26#include <vimode/searcher.h>
27
28#include <KLocalizedString>
29#include <QRegularExpression>
30#include <QString>
31
32using namespace KateVi;
33
34// TODO: the "previous word/WORD [end]" methods should be optimized. now they're being called in a
35// loop and all calculations done up to finding a match are trown away when called with a count > 1
36// because they will simply be called again from the last found position.
37// They should take the count as a parameter and collect the positions in a QList, then return
38// element (count - 1)
39
40////////////////////////////////////////////////////////////////////////////////
41// HELPER METHODS
42////////////////////////////////////////////////////////////////////////////////
43
44void ModeBase::yankToClipBoard(QChar chosen_register, const QString &text)
45{
46 // only yank to the clipboard if no register was specified,
47 // textlength > 1 and there is something else then whitespace
48 if ((chosen_register == QLatin1Char('0') || chosen_register == QLatin1Char('-') || chosen_register == PrependNumberedRegister) && text.length() > 1
49 && !text.trimmed().isEmpty()) {
50 KTextEditor::EditorPrivate::self()->copyToClipboard(text, fileName: m_view->doc()->url().fileName());
51 }
52}
53
54bool ModeBase::deleteRange(Range &r, OperationMode mode, bool addToRegister)
55{
56 r.normalize();
57 bool res = false;
58 const QString removedText = getRange(r, mode);
59
60 if (mode == LineWise) {
61 doc()->editStart();
62 for (int i = 0; i < r.endLine - r.startLine + 1; i++) {
63 res = doc()->removeLine(line: r.startLine);
64 }
65 doc()->editEnd();
66 } else {
67 res = doc()->removeText(range: r.toEditorRange(), block: mode == Block);
68 }
69
70 // the UnnamedRegister here is only a placeholder to signify that no register was selected
71 // this is needed because the fallback register depends on whether the deleted text spans a line/lines
72 QChar chosenRegister = getChosenRegister(defaultReg: UnnamedRegister);
73 if (addToRegister) {
74 fillRegister(reg: chosenRegister, text: removedText, flag: mode);
75 }
76
77 const QChar lastChar = removedText.size() > 0 ? removedText.back() : QLatin1Char('\0');
78 if (chosenRegister != BlackHoleRegister && (r.startLine != r.endLine || lastChar == QLatin1Char('\n') || lastChar == QLatin1Char('\r'))) {
79 // for deletes spanning a line/lines, always prepend to the numbered registers
80 fillRegister(reg: PrependNumberedRegister, text: removedText, flag: mode);
81 chosenRegister = PrependNumberedRegister;
82 } else if (chosenRegister == UnnamedRegister) {
83 // only set the SmallDeleteRegister when no register was selected
84 fillRegister(reg: SmallDeleteRegister, text: removedText, flag: mode);
85 chosenRegister = SmallDeleteRegister;
86 }
87 yankToClipBoard(chosen_register: chosenRegister, text: removedText);
88
89 return res;
90}
91
92const QString ModeBase::getRange(Range &r, OperationMode mode) const
93{
94 r.normalize();
95 QString s;
96
97 if (mode == LineWise) {
98 r.startColumn = 0;
99 r.endColumn = getLine(line: r.endLine).length();
100 }
101
102 if (r.motionType == InclusiveMotion) {
103 r.endColumn++;
104 }
105
106 KTextEditor::Range range = r.toEditorRange();
107 if (mode == LineWise) {
108 s = doc()->textLines(range).join(sep: QLatin1Char('\n'));
109 s.append(c: QLatin1Char('\n'));
110 } else if (mode == Block) {
111 s = doc()->text(range, blockwise: true);
112 } else {
113 s = doc()->text(range);
114 }
115
116 return s;
117}
118
119const QString ModeBase::getLine(int line) const
120{
121 return (line < 0) ? m_view->doc()->line(line: m_view->cursorPosition().line()) : doc()->line(line);
122}
123
124const QChar ModeBase::getCharUnderCursor() const
125{
126 KTextEditor::Cursor c(m_view->cursorPosition());
127
128 QString line = getLine(line: c.line());
129
130 if (line.length() == 0 && c.column() >= line.length()) {
131 return QChar::Null;
132 }
133
134 return line.at(i: c.column());
135}
136
137const QString ModeBase::getWordUnderCursor() const
138{
139 return doc()->text(range: getWordRangeUnderCursor());
140}
141
142const KTextEditor::Range ModeBase::getWordRangeUnderCursor() const
143{
144 KTextEditor::Cursor c(m_view->cursorPosition());
145
146 // find first character that is a “word letter” and start the search there
147 QChar ch = doc()->characterAt(position: c);
148 int i = 0;
149 while (!ch.isLetterOrNumber() && !ch.isMark() && ch != QLatin1Char('_') && m_extraWordCharacters.indexOf(c: ch) == -1) {
150 // advance cursor one position
151 c.setColumn(c.column() + 1);
152 if (c.column() > doc()->lineLength(line: c.line())) {
153 c.setColumn(0);
154 c.setLine(c.line() + 1);
155 if (c.line() == doc()->lines()) {
156 return KTextEditor::Range::invalid();
157 }
158 }
159
160 ch = doc()->characterAt(position: c);
161 i++; // count characters that were advanced so we know where to start the search
162 }
163
164 // move cursor the word (if cursor was placed on e.g. a paren, this will move
165 // it to the right
166 updateCursor(c);
167
168 KTextEditor::Cursor c1 = findPrevWordStart(fromLine: c.line(), fromColumn: c.column() + 1 + i, onlyCurrentLine: true);
169 KTextEditor::Cursor c2 = findWordEnd(fromLine: c1.line(), fromColumn: c1.column() + i - 1, onlyCurrentLine: true);
170 c2.setColumn(c2.column() + 1);
171
172 return KTextEditor::Range(c1, c2);
173}
174
175KTextEditor::Cursor ModeBase::findNextWordStart(int fromLine, int fromColumn, bool onlyCurrentLine) const
176{
177 QString line = getLine(line: fromLine);
178
179 // the start of word pattern need to take m_extraWordCharacters into account if defined
180 QString startOfWordPattern = QStringLiteral("\\b(\\w");
181 if (m_extraWordCharacters.length() > 0) {
182 startOfWordPattern.append(s: QLatin1String("|[") + m_extraWordCharacters + QLatin1Char(']'));
183 }
184 startOfWordPattern.append(c: QLatin1Char(')'));
185
186 const QRegularExpression startOfWord(startOfWordPattern, QRegularExpression::UseUnicodePropertiesOption); // start of a word
187 static const QRegularExpression nonSpaceAfterSpace(QStringLiteral("\\s\\S"), QRegularExpression::UseUnicodePropertiesOption); // non-space right after space
188 static const QRegularExpression nonWordAfterWord(
189 QStringLiteral("\\b(?!\\s)\\W"),
190 QRegularExpression::UseUnicodePropertiesOption); // word-boundary followed by a non-word which is not a space
191
192 int l = fromLine;
193 int c = fromColumn;
194
195 bool found = false;
196
197 while (!found) {
198 int c1 = line.indexOf(re: startOfWord, from: c + 1);
199 int c2 = line.indexOf(re: nonSpaceAfterSpace, from: c);
200 int c3 = line.indexOf(re: nonWordAfterWord, from: c + 1);
201
202 if (c1 == -1 && c2 == -1 && c3 == -1) {
203 if (onlyCurrentLine) {
204 return KTextEditor::Cursor::invalid();
205 } else if (l >= doc()->lines() - 1) {
206 c = qMax(a: line.length() - 1, b: 0);
207 return KTextEditor::Cursor::invalid();
208 } else {
209 c = 0;
210 l++;
211
212 line = getLine(line: l);
213
214 if (line.length() == 0 || !line.at(i: c).isSpace()) {
215 found = true;
216 }
217
218 continue;
219 }
220 }
221
222 c2++; // the second regexp will match one character *before* the character we want to go to
223
224 if (c1 <= 0) {
225 c1 = line.length() - 1;
226 }
227 if (c2 <= 0) {
228 c2 = line.length() - 1;
229 }
230 if (c3 <= 0) {
231 c3 = line.length() - 1;
232 }
233
234 c = qMin(a: c1, b: qMin(a: c2, b: c3));
235
236 found = true;
237 }
238
239 return KTextEditor::Cursor(l, c);
240}
241
242KTextEditor::Cursor ModeBase::findNextWORDStart(int fromLine, int fromColumn, bool onlyCurrentLine) const
243{
244 QString line = getLine();
245
246 int l = fromLine;
247 int c = fromColumn;
248
249 bool found = false;
250 static const QRegularExpression startOfWORD(QStringLiteral("\\s\\S"), QRegularExpression::UseUnicodePropertiesOption);
251
252 while (!found) {
253 c = line.indexOf(re: startOfWORD, from: c);
254
255 if (c == -1) {
256 if (onlyCurrentLine) {
257 return KTextEditor::Cursor(l, c);
258 } else if (l >= doc()->lines() - 1) {
259 c = line.length() - 1;
260 break;
261 } else {
262 c = 0;
263 l++;
264
265 line = getLine(line: l);
266
267 if (line.length() == 0 || !line.at(i: c).isSpace()) {
268 found = true;
269 }
270
271 continue;
272 }
273 } else {
274 c++;
275 found = true;
276 }
277 }
278
279 return KTextEditor::Cursor(l, c);
280}
281
282KTextEditor::Cursor ModeBase::findPrevWordEnd(int fromLine, int fromColumn, bool onlyCurrentLine) const
283{
284 QString line = getLine(line: fromLine);
285
286 QString endOfWordPattern = QStringLiteral("\\S\\s|\\S$|\\S\\b|\\w\\W|^$");
287
288 if (m_extraWordCharacters.length() > 0) {
289 endOfWordPattern.append(s: QLatin1String("|[") + m_extraWordCharacters + QLatin1String("][^") + m_extraWordCharacters + QLatin1Char(']'));
290 }
291
292 const QRegularExpression endOfWord(endOfWordPattern, QRegularExpression::UseUnicodePropertiesOption);
293
294 int l = fromLine;
295 int c = fromColumn;
296
297 bool found = false;
298
299 while (!found) {
300 int c1 = line.lastIndexOf(re: endOfWord, from: c - 1);
301
302 if (c1 != -1 && c - 1 != -1) {
303 found = true;
304 c = c1;
305 } else {
306 if (onlyCurrentLine) {
307 return KTextEditor::Cursor::invalid();
308 } else if (l > 0) {
309 line = getLine(line: --l);
310 c = line.length();
311
312 continue;
313 } else {
314 return KTextEditor::Cursor::invalid();
315 }
316 }
317 }
318
319 return KTextEditor::Cursor(l, c);
320}
321
322KTextEditor::Cursor ModeBase::findPrevWORDEnd(int fromLine, int fromColumn, bool onlyCurrentLine) const
323{
324 QString line = getLine(line: fromLine);
325
326 static const QRegularExpression endOfWORDPattern(QStringLiteral("\\S\\s|\\S$|^$"), QRegularExpression::UseUnicodePropertiesOption);
327
328 int l = fromLine;
329 int c = fromColumn;
330
331 bool found = false;
332
333 while (!found) {
334 int c1 = line.lastIndexOf(re: endOfWORDPattern, from: c - 1);
335
336 if (c1 != -1 && c - 1 != -1) {
337 found = true;
338 c = c1;
339 } else {
340 if (onlyCurrentLine) {
341 return KTextEditor::Cursor::invalid();
342 } else if (l > 0) {
343 line = getLine(line: --l);
344 c = line.length();
345
346 continue;
347 } else {
348 c = 0;
349 return KTextEditor::Cursor::invalid();
350 }
351 }
352 }
353
354 return KTextEditor::Cursor(l, c);
355}
356
357KTextEditor::Cursor ModeBase::findPrevWordStart(int fromLine, int fromColumn, bool onlyCurrentLine) const
358{
359 QString line = getLine(line: fromLine);
360
361 // the start of word pattern need to take m_extraWordCharacters into account if defined
362 QString startOfWordPattern = QStringLiteral("\\b(\\w");
363 if (m_extraWordCharacters.length() > 0) {
364 startOfWordPattern.append(s: QLatin1String("|[") + m_extraWordCharacters + QLatin1Char(']'));
365 }
366 startOfWordPattern.append(c: QLatin1Char(')'));
367
368 const QRegularExpression startOfWord(startOfWordPattern, QRegularExpression::UseUnicodePropertiesOption); // start of a word
369 static const QRegularExpression nonSpaceAfterSpace(QStringLiteral("\\s\\S"), QRegularExpression::UseUnicodePropertiesOption); // non-space right after space
370 static const QRegularExpression nonWordAfterWord(
371 QStringLiteral("\\b(?!\\s)\\W"),
372 QRegularExpression::UseUnicodePropertiesOption); // word-boundary followed by a non-word which is not a space
373 static const QRegularExpression startOfLine(QStringLiteral("^\\S"), QRegularExpression::UseUnicodePropertiesOption); // non-space at start of line
374
375 int l = fromLine;
376 int c = fromColumn;
377
378 bool found = false;
379
380 while (!found) {
381 int c1 = (c > 0) ? line.lastIndexOf(re: startOfWord, from: c - 1) : -1;
382 int c2 = (c > 1) ? line.lastIndexOf(re: nonSpaceAfterSpace, from: c - 2) : -1;
383 int c3 = (c > 0) ? line.lastIndexOf(re: nonWordAfterWord, from: c - 1) : -1;
384 int c4 = (c > 0) ? line.lastIndexOf(re: startOfLine, from: c - 1) : -1;
385
386 if (c1 == -1 && c2 == -1 && c3 == -1 && c4 == -1) {
387 if (onlyCurrentLine) {
388 return KTextEditor::Cursor::invalid();
389 } else if (l <= 0) {
390 return KTextEditor::Cursor::invalid();
391 } else {
392 line = getLine(line: --l);
393 c = line.length();
394
395 if (line.length() == 0) {
396 c = 0;
397 found = true;
398 }
399
400 continue;
401 }
402 }
403
404 c2++; // the second regexp will match one character *before* the character we want to go to
405
406 if (c1 <= 0) {
407 c1 = 0;
408 }
409 if (c2 <= 0) {
410 c2 = 0;
411 }
412 if (c3 <= 0) {
413 c3 = 0;
414 }
415 if (c4 <= 0) {
416 c4 = 0;
417 }
418
419 c = qMax(a: c1, b: qMax(a: c2, b: qMax(a: c3, b: c4)));
420
421 found = true;
422 }
423
424 return KTextEditor::Cursor(l, c);
425}
426
427KTextEditor::Cursor ModeBase::findPrevWORDStart(int fromLine, int fromColumn, bool onlyCurrentLine) const
428{
429 QString line = getLine(line: fromLine);
430
431 static const QRegularExpression startOfWORD(QStringLiteral("\\s\\S"), QRegularExpression::UseUnicodePropertiesOption);
432 static const QRegularExpression startOfLineWORD(QStringLiteral("^\\S"), QRegularExpression::UseUnicodePropertiesOption);
433
434 int l = fromLine;
435 int c = fromColumn;
436
437 bool found = false;
438
439 while (!found) {
440 int c1 = (c > 1) ? line.lastIndexOf(re: startOfWORD, from: c - 2) : -1;
441 int c2 = (c > 0) ? line.lastIndexOf(re: startOfLineWORD, from: c - 1) : -1;
442
443 if (c1 == -1 && c2 == -1) {
444 if (onlyCurrentLine) {
445 return KTextEditor::Cursor::invalid();
446 } else if (l <= 0) {
447 return KTextEditor::Cursor::invalid();
448 } else {
449 line = getLine(line: --l);
450 c = line.length();
451
452 if (line.length() == 0) {
453 c = 0;
454 found = true;
455 }
456
457 continue;
458 }
459 }
460
461 c1++; // the startOfWORD pattern matches one character before the word
462
463 c = qMax(a: c1, b: c2);
464
465 if (c <= 0) {
466 c = 0;
467 }
468
469 found = true;
470 }
471
472 return KTextEditor::Cursor(l, c);
473}
474
475KTextEditor::Cursor ModeBase::findWordEnd(int fromLine, int fromColumn, bool onlyCurrentLine) const
476{
477 QString line = getLine(line: fromLine);
478
479 QString endOfWordPattern = QStringLiteral("\\S\\s|\\S$|\\w\\W|\\S\\b");
480
481 if (m_extraWordCharacters.length() > 0) {
482 endOfWordPattern.append(s: QLatin1String("|[") + m_extraWordCharacters + QLatin1String("][^") + m_extraWordCharacters + QLatin1Char(']'));
483 }
484
485 const QRegularExpression endOfWORD(endOfWordPattern, QRegularExpression::UseUnicodePropertiesOption);
486
487 int l = fromLine;
488 int c = fromColumn;
489
490 bool found = false;
491
492 while (!found) {
493 int c1 = line.indexOf(re: endOfWORD, from: c + 1);
494
495 if (c1 != -1) {
496 found = true;
497 c = c1;
498 } else {
499 if (onlyCurrentLine) {
500 return KTextEditor::Cursor::invalid();
501 } else if (l >= doc()->lines() - 1) {
502 c = line.length() - 1;
503 return KTextEditor::Cursor::invalid();
504 } else {
505 c = -1;
506 line = getLine(line: ++l);
507
508 continue;
509 }
510 }
511 }
512
513 return KTextEditor::Cursor(l, c);
514}
515
516KTextEditor::Cursor ModeBase::findWORDEnd(int fromLine, int fromColumn, bool onlyCurrentLine) const
517{
518 QString line = getLine(line: fromLine);
519
520 static const QRegularExpression endOfWORD(QStringLiteral("\\S\\s|\\S$"), QRegularExpression::UseUnicodePropertiesOption);
521
522 int l = fromLine;
523 int c = fromColumn;
524
525 bool found = false;
526
527 while (!found) {
528 int c1 = line.indexOf(re: endOfWORD, from: c + 1);
529
530 if (c1 != -1) {
531 found = true;
532 c = c1;
533 } else {
534 if (onlyCurrentLine) {
535 return KTextEditor::Cursor::invalid();
536 } else if (l >= doc()->lines() - 1) {
537 c = line.length() - 1;
538 return KTextEditor::Cursor::invalid();
539 } else {
540 c = -1;
541 line = getLine(line: ++l);
542
543 continue;
544 }
545 }
546 }
547
548 return KTextEditor::Cursor(l, c);
549}
550
551Range innerRange(Range range, bool inner)
552{
553 Range r = range;
554
555 if (inner) {
556 const int columnDistance = qAbs(t: r.startColumn - r.endColumn);
557 if ((r.startLine == r.endLine) && columnDistance == 1) {
558 // Start and end are right next to each other; there is nothing inside them.
559 return Range::invalid();
560 }
561 r.startColumn++;
562 r.endColumn--;
563 }
564
565 return r;
566}
567
568Range ModeBase::findSurroundingQuotes(const QChar &c, bool inner) const
569{
570 KTextEditor::Cursor cursor(m_view->cursorPosition());
571 Range r;
572 r.startLine = cursor.line();
573 r.endLine = cursor.line();
574
575 QString line = doc()->line(line: cursor.line());
576
577 // If cursor on the quote we should choose the best direction.
578 if (line.at(i: cursor.column()) == c) {
579 int attribute = m_view->doc()->kateTextLine(i: cursor.line()).attribute(pos: cursor.column());
580
581 // If at the beginning of the line - then we might search the end.
582 if (doc()->kateTextLine(i: cursor.line()).attribute(pos: cursor.column() + 1) == attribute
583 && doc()->kateTextLine(i: cursor.line()).attribute(pos: cursor.column() - 1) != attribute) {
584 r.startColumn = cursor.column();
585 r.endColumn = line.indexOf(c, from: cursor.column() + 1);
586
587 return innerRange(range: r, inner);
588 }
589
590 // If at the end of the line - then we might search the beginning.
591 if (doc()->kateTextLine(i: cursor.line()).attribute(pos: cursor.column() + 1) != attribute
592 && doc()->kateTextLine(i: cursor.line()).attribute(pos: cursor.column() - 1) == attribute) {
593 r.startColumn = line.lastIndexOf(c, from: cursor.column() - 1);
594 r.endColumn = cursor.column();
595
596 return innerRange(range: r, inner);
597 }
598 // Try to search the quote to right
599 int c1 = line.indexOf(c, from: cursor.column() + 1);
600 if (c1 != -1) {
601 r.startColumn = cursor.column();
602 r.endColumn = c1;
603
604 return innerRange(range: r, inner);
605 }
606
607 // Try to search the quote to left
608 int c2 = line.lastIndexOf(c, from: cursor.column() - 1);
609 if (c2 != -1) {
610 r.startColumn = c2;
611 r.endColumn = cursor.column();
612
613 return innerRange(range: r, inner);
614 }
615
616 // Nothing found - give up :)
617 return Range::invalid();
618 }
619
620 r.startColumn = line.lastIndexOf(c, from: cursor.column());
621 r.endColumn = line.indexOf(c, from: cursor.column());
622
623 if (r.startColumn == -1 || r.endColumn == -1 || r.startColumn > r.endColumn) {
624 return Range::invalid();
625 }
626
627 return innerRange(range: r, inner);
628}
629
630Range ModeBase::findSurroundingBrackets(const QChar &c1, const QChar &c2, bool inner, const QChar &nested1, const QChar &nested2) const
631{
632 KTextEditor::Cursor cursor(m_view->cursorPosition());
633 Range r(cursor, InclusiveMotion);
634 int line = cursor.line();
635 int column = cursor.column();
636 int catalan;
637
638 // Chars should not differ. For equal chars use findSurroundingQuotes.
639 Q_ASSERT(c1 != c2);
640
641 const QString &l = m_view->doc()->line(line);
642 if (column < l.size() && l.at(i: column) == c2) {
643 r.endLine = line;
644 r.endColumn = column;
645 } else {
646 if (column < l.size() && l.at(i: column) == c1) {
647 column++;
648 }
649
650 for (catalan = 1; line < m_view->doc()->lines(); line++) {
651 const QString &l = m_view->doc()->line(line);
652
653 for (; column < l.size(); column++) {
654 const QChar &c = l.at(i: column);
655
656 if (c == nested1) {
657 catalan++;
658 } else if (c == nested2) {
659 catalan--;
660 }
661 if (!catalan) {
662 break;
663 }
664 }
665 if (!catalan) {
666 break;
667 }
668 column = 0;
669 }
670
671 if (catalan != 0) {
672 return Range::invalid();
673 }
674 r.endLine = line;
675 r.endColumn = column;
676 }
677
678 // Same algorithm but backwards.
679 line = cursor.line();
680 column = cursor.column();
681
682 if (column < l.size() && l.at(i: column) == c1) {
683 r.startLine = line;
684 r.startColumn = column;
685 } else {
686 if (column < l.size() && l.at(i: column) == c2) {
687 column--;
688 }
689
690 for (catalan = 1; line >= 0; line--) {
691 const QString &l = m_view->doc()->line(line);
692
693 for (; column >= 0; column--) {
694 const QChar &c = l.at(i: column);
695
696 if (c == nested1) {
697 catalan--;
698 } else if (c == nested2) {
699 catalan++;
700 }
701 if (!catalan) {
702 break;
703 }
704 }
705 if (!catalan || !line) {
706 break;
707 }
708 column = m_view->doc()->line(line: line - 1).size() - 1;
709 }
710 if (catalan != 0) {
711 return Range::invalid();
712 }
713 r.startColumn = column;
714 r.startLine = line;
715 }
716
717 return innerRange(range: r, inner);
718}
719
720Range ModeBase::findSurrounding(const QRegularExpression &c1, const QRegularExpression &c2, bool inner) const
721{
722 KTextEditor::Cursor cursor(m_view->cursorPosition());
723 QString line = getLine();
724
725 int col1 = line.lastIndexOf(re: c1, from: cursor.column());
726 int col2 = line.indexOf(re: c2, from: cursor.column());
727
728 Range r(cursor.line(), col1, cursor.line(), col2, InclusiveMotion);
729
730 if (col1 == -1 || col2 == -1 || col1 > col2) {
731 return Range::invalid();
732 }
733
734 if (inner) {
735 r.startColumn++;
736 r.endColumn--;
737 }
738
739 return r;
740}
741
742int ModeBase::findLineStartingWitchChar(const QChar &c, int count, bool forward) const
743{
744 int line = m_view->cursorPosition().line();
745 int lines = doc()->lines();
746 int hits = 0;
747
748 if (forward) {
749 line++;
750 } else {
751 line--;
752 }
753
754 while (line < lines && line >= 0 && hits < count) {
755 QString l = getLine(line);
756 if (l.length() > 0 && l.at(i: 0) == c) {
757 hits++;
758 }
759 if (hits != count) {
760 if (forward) {
761 line++;
762 } else {
763 line--;
764 }
765 }
766 }
767
768 if (hits == getCount()) {
769 return line;
770 }
771
772 return -1;
773}
774
775void ModeBase::updateCursor(const KTextEditor::Cursor c) const
776{
777 m_viInputModeManager->updateCursor(c);
778}
779
780/**
781 * @return the register given for the command. If no register was given, defaultReg is returned.
782 */
783QChar ModeBase::getChosenRegister(const QChar &defaultReg) const
784{
785 return (m_register != QChar::Null) ? m_register : defaultReg;
786}
787
788QString ModeBase::getRegisterContent(const QChar &reg)
789{
790 QString r = m_viInputModeManager->globalState()->registers()->getContent(reg);
791
792 if (r.isNull()) {
793 error(i18n("Nothing in register %1", reg.toLower()));
794 }
795
796 return r;
797}
798
799OperationMode ModeBase::getRegisterFlag(const QChar &reg) const
800{
801 return m_viInputModeManager->globalState()->registers()->getFlag(reg);
802}
803
804void ModeBase::fillRegister(const QChar &reg, const QString &text, OperationMode flag)
805{
806 m_viInputModeManager->globalState()->registers()->set(reg, text, flag);
807}
808
809KTextEditor::Cursor ModeBase::getNextJump(KTextEditor::Cursor cursor) const
810{
811 return m_viInputModeManager->jumps()->next(cursor);
812}
813
814KTextEditor::Cursor ModeBase::getPrevJump(KTextEditor::Cursor cursor) const
815{
816 return m_viInputModeManager->jumps()->prev(cursor);
817}
818
819Range ModeBase::goLineDown()
820{
821 return goLineUpDown(lines: getCount());
822}
823
824Range ModeBase::goLineUp()
825{
826 return goLineUpDown(lines: -getCount());
827}
828
829/**
830 * method for moving up or down one or more lines
831 * note: the sticky column is always a virtual column
832 */
833Range ModeBase::goLineUpDown(int lines)
834{
835 KTextEditor::Cursor c(m_view->cursorPosition());
836 Range r(c, InclusiveMotion);
837 int tabstop = doc()->config()->tabWidth();
838
839 // if in an empty document, just return
840 if (lines == 0) {
841 return r;
842 }
843
844 r.endLine += lines;
845
846 // limit end line to be from line 0 through the last line
847 if (r.endLine < 0) {
848 r.endLine = 0;
849 } else if (r.endLine > doc()->lines() - 1) {
850 r.endLine = doc()->lines() - 1;
851 }
852
853 Kate::TextLine startLine = doc()->plainKateTextLine(i: c.line());
854 Kate::TextLine endLine = doc()->plainKateTextLine(i: r.endLine);
855
856 int endLineLen = doc()->lineLength(line: r.endLine) - 1;
857
858 if (endLineLen < 0) {
859 endLineLen = 0;
860 }
861
862 int endLineLenVirt = endLine.toVirtualColumn(column: endLineLen, tabWidth: tabstop);
863 int virtColumnStart = startLine.toVirtualColumn(column: c.column(), tabWidth: tabstop);
864
865 // if sticky column isn't set, set end column and set sticky column to its virtual column
866 if (m_stickyColumn == -1) {
867 r.endColumn = endLine.fromVirtualColumn(column: virtColumnStart, tabWidth: tabstop);
868 m_stickyColumn = virtColumnStart;
869 } else {
870 // sticky is set - set end column to its value
871 r.endColumn = endLine.fromVirtualColumn(column: m_stickyColumn, tabWidth: tabstop);
872 }
873
874 // make sure end column won't be after the last column of a line
875 if (r.endColumn > endLineLen) {
876 r.endColumn = endLineLen;
877 }
878
879 // if we move to a line shorter than the current column, go to its end
880 if (virtColumnStart > endLineLenVirt) {
881 r.endColumn = endLineLen;
882 }
883
884 return r;
885}
886
887Range ModeBase::goVisualLineUpDown(int lines)
888{
889 KTextEditor::Cursor c(m_view->cursorPosition());
890 Range r(c, InclusiveMotion);
891 int tabstop = doc()->config()->tabWidth();
892
893 if (lines == 0) {
894 // We're not moving anywhere.
895 return r;
896 }
897
898 KateLayoutCache *cache = m_viInputModeManager->inputAdapter()->layoutCache();
899
900 // Work out the real and visual line pair of the beginning of the visual line we'd end up
901 // on by moving lines visual lines. We ignore the column, for now.
902 int finishVisualLine = cache->viewLine(realCursor: m_view->cursorPosition());
903 int finishRealLine = m_view->cursorPosition().line();
904 int count = qAbs(t: lines);
905 bool invalidPos = false;
906 if (lines > 0) {
907 // Find the beginning of the visual line "lines" visual lines down.
908 while (count > 0) {
909 finishVisualLine++;
910 const KateLineLayout *lineLayout = cache->line(realLine: finishRealLine);
911 if (lineLayout && finishVisualLine >= lineLayout->viewLineCount()) {
912 finishRealLine++;
913 finishVisualLine = 0;
914 }
915 if (finishRealLine >= doc()->lines()) {
916 invalidPos = true;
917 break;
918 }
919 count--;
920 }
921 } else {
922 // Find the beginning of the visual line "lines" visual lines up.
923 while (count > 0) {
924 finishVisualLine--;
925 if (finishVisualLine < 0) {
926 finishRealLine--;
927 if (finishRealLine < 0) {
928 invalidPos = true;
929 break;
930 }
931 const auto lineLayout = cache->line(realLine: finishRealLine);
932 if (!lineLayout) {
933 finishVisualLine = 0;
934 continue;
935 }
936 finishVisualLine = lineLayout->viewLineCount() - 1;
937 }
938 count--;
939 }
940 }
941 if (invalidPos) {
942 r.endLine = -1;
943 r.endColumn = -1;
944 return r;
945 }
946
947 // We know the final (real) line ...
948 r.endLine = finishRealLine;
949 // ... now work out the final (real) column.
950
951 if (m_stickyColumn == -1 || !m_lastMotionWasVisualLineUpOrDown) {
952 // Compute new sticky column. It is a *visual* sticky column.
953 int startVisualLine = cache->viewLine(realCursor: m_view->cursorPosition());
954 int startRealLine = m_view->cursorPosition().line();
955 const Kate::TextLine startLine = doc()->plainKateTextLine(i: c.line());
956 // Adjust for the fact that if the portion of the line before wrapping is indented,
957 // the continuations are also "invisibly" (i.e. without any spaces in the text itself) indented.
958 const bool isWrappedContinuation = (cache->textLayout(realLine: startRealLine, viewLine: startVisualLine).lineLayout().lineNumber() != 0);
959 const int numInvisibleIndentChars = [&] {
960 if (isWrappedContinuation) {
961 auto l = doc()->plainKateTextLine(i: startRealLine);
962 return startLine.toVirtualColumn(column: l.nextNonSpaceChar(pos: 0), tabWidth: tabstop);
963 }
964 return 0;
965 }();
966
967 const int realLineStartColumn = cache->textLayout(realLine: startRealLine, viewLine: startVisualLine).startCol();
968 const int lineStartVirtualColumn = startLine.toVirtualColumn(column: realLineStartColumn, tabWidth: tabstop);
969 const int visualColumnNoInvisibleIndent = startLine.toVirtualColumn(column: c.column(), tabWidth: tabstop) - lineStartVirtualColumn;
970 m_stickyColumn = visualColumnNoInvisibleIndent + numInvisibleIndentChars;
971 Q_ASSERT(m_stickyColumn >= 0);
972 }
973
974 // The "real" (non-virtual) beginning of the current "line", which might be a wrapped continuation of a
975 // "real" line.
976 const int realLineStartColumn = cache->textLayout(realLine: finishRealLine, viewLine: finishVisualLine).startCol();
977 const Kate::TextLine endLine = doc()->plainKateTextLine(i: r.endLine);
978 // Adjust for the fact that if the portion of the line before wrapping is indented,
979 // the continuations are also "invisibly" (i.e. without any spaces in the text itself) indented.
980 const bool isWrappedContinuation = (cache->textLayout(realLine: finishRealLine, viewLine: finishVisualLine).lineLayout().lineNumber() != 0);
981 const int numInvisibleIndentChars = [&] {
982 if (isWrappedContinuation) {
983 auto l = doc()->plainKateTextLine(i: finishRealLine);
984 return endLine.toVirtualColumn(column: l.nextNonSpaceChar(pos: 0), tabWidth: tabstop);
985 }
986 return 0;
987 }();
988 if (m_stickyColumn == (unsigned int)KateVi::EOL) {
989 const int visualEndColumn = cache->textLayout(realLine: finishRealLine, viewLine: finishVisualLine).lineLayout().textLength() - 1;
990 r.endColumn = endLine.fromVirtualColumn(column: visualEndColumn + realLineStartColumn - numInvisibleIndentChars, tabWidth: tabstop);
991 } else {
992 // Algorithm: find the "real" column corresponding to the start of the line. Offset from that
993 // until the "visual" column is equal to the "visual" sticky column.
994 int realOffsetToVisualStickyColumn = 0;
995 const int lineStartVirtualColumn = endLine.toVirtualColumn(column: realLineStartColumn, tabWidth: tabstop);
996 while (true) {
997 const int visualColumn =
998 endLine.toVirtualColumn(column: realLineStartColumn + realOffsetToVisualStickyColumn, tabWidth: tabstop) - lineStartVirtualColumn + numInvisibleIndentChars;
999 if (visualColumn >= m_stickyColumn) {
1000 break;
1001 }
1002 realOffsetToVisualStickyColumn++;
1003 }
1004 r.endColumn = realLineStartColumn + realOffsetToVisualStickyColumn;
1005 }
1006 m_currentMotionWasVisualLineUpOrDown = true;
1007
1008 return r;
1009}
1010
1011bool ModeBase::startNormalMode()
1012{
1013 /* store the key presses for this "insert mode session" so that it can be repeated with the
1014 * '.' command
1015 * - ignore transition from Visual Modes
1016 */
1017 if (!(m_viInputModeManager->isAnyVisualMode() || m_viInputModeManager->lastChangeRecorder()->isReplaying())) {
1018 m_viInputModeManager->storeLastChangeCommand();
1019 m_viInputModeManager->clearCurrentChangeLog();
1020 }
1021
1022 m_viInputModeManager->viEnterNormalMode();
1023 m_view->doc()->setUndoMergeAllEdits(false);
1024 Q_EMIT m_view->viewModeChanged(view: m_view, mode: m_view->viewMode());
1025
1026 return true;
1027}
1028
1029bool ModeBase::startInsertMode()
1030{
1031 m_viInputModeManager->viEnterInsertMode();
1032 m_view->doc()->setUndoMergeAllEdits(true);
1033 Q_EMIT m_view->viewModeChanged(view: m_view, mode: m_view->viewMode());
1034
1035 return true;
1036}
1037
1038bool ModeBase::startReplaceMode()
1039{
1040 m_view->doc()->setUndoMergeAllEdits(true);
1041 m_viInputModeManager->viEnterReplaceMode();
1042 Q_EMIT m_view->viewModeChanged(view: m_view, mode: m_view->viewMode());
1043
1044 return true;
1045}
1046
1047bool ModeBase::startVisualMode()
1048{
1049 if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualLineMode) {
1050 m_viInputModeManager->getViVisualMode()->setVisualModeType(ViMode::VisualMode);
1051 m_viInputModeManager->changeViMode(newMode: ViMode::VisualMode);
1052 } else if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualBlockMode) {
1053 m_viInputModeManager->getViVisualMode()->setVisualModeType(ViMode::VisualMode);
1054 m_viInputModeManager->changeViMode(newMode: ViMode::VisualMode);
1055 } else {
1056 m_viInputModeManager->viEnterVisualMode();
1057 }
1058
1059 Q_EMIT m_view->viewModeChanged(view: m_view, mode: m_view->viewMode());
1060
1061 return true;
1062}
1063
1064bool ModeBase::startVisualBlockMode()
1065{
1066 if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualMode) {
1067 m_viInputModeManager->getViVisualMode()->setVisualModeType(ViMode::VisualBlockMode);
1068 m_viInputModeManager->changeViMode(newMode: ViMode::VisualBlockMode);
1069 } else {
1070 m_viInputModeManager->viEnterVisualMode(visualMode: ViMode::VisualBlockMode);
1071 }
1072
1073 Q_EMIT m_view->viewModeChanged(view: m_view, mode: m_view->viewMode());
1074
1075 return true;
1076}
1077
1078bool ModeBase::startVisualLineMode()
1079{
1080 if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualMode) {
1081 m_viInputModeManager->getViVisualMode()->setVisualModeType(ViMode::VisualLineMode);
1082 m_viInputModeManager->changeViMode(newMode: ViMode::VisualLineMode);
1083 } else {
1084 m_viInputModeManager->viEnterVisualMode(visualMode: ViMode::VisualLineMode);
1085 }
1086
1087 Q_EMIT m_view->viewModeChanged(view: m_view, mode: m_view->viewMode());
1088
1089 return true;
1090}
1091
1092void ModeBase::error(const QString &errorMsg)
1093{
1094 delete m_infoMessage;
1095
1096 m_infoMessage = new KTextEditor::Message(errorMsg, KTextEditor::Message::Error);
1097 m_infoMessage->setPosition(KTextEditor::Message::BottomInView);
1098 m_infoMessage->setAutoHide(2000); // 2 seconds
1099 m_infoMessage->setView(m_view);
1100
1101 m_view->doc()->postMessage(message: m_infoMessage);
1102}
1103
1104void ModeBase::message(const QString &msg)
1105{
1106 delete m_infoMessage;
1107
1108 m_infoMessage = new KTextEditor::Message(msg, KTextEditor::Message::Positive);
1109 m_infoMessage->setPosition(KTextEditor::Message::BottomInView);
1110 m_infoMessage->setAutoHide(2000); // 2 seconds
1111 m_infoMessage->setView(m_view);
1112
1113 m_view->doc()->postMessage(message: m_infoMessage);
1114}
1115
1116QString ModeBase::getVerbatimKeys() const
1117{
1118 return m_keysVerbatim;
1119}
1120
1121const QChar ModeBase::getCharAtVirtualColumn(const QString &line, int virtualColumn, int tabWidth)
1122{
1123 int column = 0;
1124 int tempCol = 0;
1125
1126 // sanity check: if the line is empty, there are no chars
1127 if (line.length() == 0) {
1128 return QChar::Null;
1129 }
1130
1131 while (tempCol < virtualColumn) {
1132 if (line.at(i: column) == QLatin1Char('\t')) {
1133 tempCol += tabWidth - (tempCol % tabWidth);
1134 } else {
1135 tempCol++;
1136 }
1137
1138 if (tempCol <= virtualColumn) {
1139 column++;
1140
1141 if (column >= line.length()) {
1142 return QChar::Null;
1143 }
1144 }
1145 }
1146
1147 if (line.length() > column) {
1148 return line.at(i: column);
1149 }
1150
1151 return QChar::Null;
1152}
1153
1154void ModeBase::addToNumberUnderCursor(int count)
1155{
1156 KTextEditor::Cursor c(m_view->cursorPosition());
1157 QString line = getLine();
1158
1159 if (line.isEmpty()) {
1160 return;
1161 }
1162
1163 const int cursorColumn = c.column();
1164 const int cursorLine = c.line();
1165 const KTextEditor::Cursor prevWordStart = findPrevWordStart(fromLine: cursorLine, fromColumn: cursorColumn);
1166 int wordStartPos = prevWordStart.column();
1167 if (prevWordStart.line() < cursorLine) {
1168 // The previous word starts on the previous line: ignore.
1169 wordStartPos = 0;
1170 }
1171 if (wordStartPos > 0 && line.at(i: wordStartPos - 1) == QLatin1Char('-')) {
1172 wordStartPos--;
1173 }
1174
1175 int numberStartPos = -1;
1176 QString numberAsString;
1177 static const QRegularExpression numberRegex(QStringLiteral("0x[0-9a-fA-F]+|\\-?\\d+"));
1178 auto numberMatchIter = numberRegex.globalMatch(subject: line, offset: wordStartPos);
1179 while (numberMatchIter.hasNext()) {
1180 const auto numberMatch = numberMatchIter.next();
1181 const bool numberEndedBeforeCursor = (numberMatch.capturedStart() + numberMatch.capturedLength() <= cursorColumn);
1182 if (!numberEndedBeforeCursor) {
1183 // This is the first number-like string under or after the cursor - this'll do!
1184 numberStartPos = numberMatch.capturedStart();
1185 numberAsString = numberMatch.captured();
1186 break;
1187 }
1188 }
1189
1190 if (numberStartPos == -1) {
1191 // None found.
1192 return;
1193 }
1194
1195 bool parsedNumberSuccessfully = false;
1196 int base = numberAsString.startsWith(s: QLatin1String("0x")) ? 16 : 10;
1197 if (base != 16 && numberAsString.startsWith(c: QLatin1Char('0')) && numberAsString.length() > 1) {
1198 // If a non-hex number with a leading 0 can be parsed as octal, then assume
1199 // it is octal.
1200 numberAsString.toInt(ok: &parsedNumberSuccessfully, base: 8);
1201 if (parsedNumberSuccessfully) {
1202 base = 8;
1203 }
1204 }
1205 const int originalNumber = numberAsString.toInt(ok: &parsedNumberSuccessfully, base);
1206
1207 if (!parsedNumberSuccessfully) {
1208 // conversion to int failed. give up.
1209 return;
1210 }
1211
1212 QString basePrefix;
1213 if (base == 16) {
1214 basePrefix = QStringLiteral("0x");
1215 } else if (base == 8) {
1216 basePrefix = QStringLiteral("0");
1217 }
1218
1219 const int withoutBaseLength = numberAsString.length() - basePrefix.length();
1220
1221 const int newNumber = originalNumber + count;
1222
1223 // Create the new text string to be inserted. Prepend with “0x” if in base 16, and "0" if base 8.
1224 // For non-decimal numbers, try to keep the length of the number the same (including leading 0's).
1225 const QString newNumberPadded =
1226 (base == 10) ? QStringLiteral("%1").arg(a: newNumber, fieldWidth: 0, base) : QStringLiteral("%1").arg(a: newNumber, fieldWidth: withoutBaseLength, base, fillChar: QLatin1Char('0'));
1227 const QString newNumberText = basePrefix + newNumberPadded;
1228
1229 // Replace the old number string with the new.
1230 doc()->editStart();
1231 doc()->removeText(range: KTextEditor::Range(cursorLine, numberStartPos, cursorLine, numberStartPos + numberAsString.length()));
1232 doc()->insertText(position: KTextEditor::Cursor(cursorLine, numberStartPos), s: newNumberText);
1233 doc()->editEnd();
1234 updateCursor(c: KTextEditor::Cursor(m_view->cursorPosition().line(), numberStartPos + newNumberText.length() - 1));
1235}
1236
1237void ModeBase::switchView(Direction direction)
1238{
1239 std::vector<KTextEditor::ViewPrivate *> visible_views;
1240 for (KTextEditor::ViewPrivate *view : KTextEditor::EditorPrivate::self()->views()) {
1241 if (view->isVisible()) {
1242 visible_views.push_back(x: view);
1243 }
1244 }
1245
1246 QPoint current_point = m_view->mapToGlobal(m_view->pos());
1247 int curr_x1 = current_point.x();
1248 int curr_x2 = current_point.x() + m_view->width();
1249 int curr_y1 = current_point.y();
1250 int curr_y2 = current_point.y() + m_view->height();
1251 const KTextEditor::Cursor cursorPos = m_view->cursorPosition();
1252 const QPoint globalPos = m_view->mapToGlobal(m_view->cursorToCoordinate(cursor: cursorPos));
1253 int curr_cursor_y = globalPos.y();
1254 int curr_cursor_x = globalPos.x();
1255
1256 KTextEditor::ViewPrivate *bestview = nullptr;
1257 int best_x1 = -1;
1258 int best_x2 = -1;
1259 int best_y1 = -1;
1260 int best_y2 = -1;
1261 int best_center_y = -1;
1262 int best_center_x = -1;
1263
1264 if (direction == Next && visible_views.size() != 1) {
1265 for (size_t i = 0; i < visible_views.size(); i++) {
1266 if (visible_views.at(n: i) == m_view) {
1267 if (i != visible_views.size() - 1) {
1268 bestview = visible_views.at(n: i + 1);
1269 } else {
1270 bestview = visible_views.at(n: 0);
1271 }
1272 }
1273 }
1274 } else {
1275 for (KTextEditor::ViewPrivate *view : visible_views) {
1276 QPoint point = view->mapToGlobal(view->pos());
1277 int x1 = point.x();
1278 int x2 = point.x() + view->width();
1279 int y1 = point.y();
1280 int y2 = point.y() + m_view->height();
1281 int center_y = (y1 + y2) / 2;
1282 int center_x = (x1 + x2) / 2;
1283
1284 switch (direction) {
1285 case Left:
1286 if (view != m_view && x2 <= curr_x1
1287 && (x2 > best_x2 || (x2 == best_x2 && qAbs(t: curr_cursor_y - center_y) < qAbs(t: curr_cursor_y - best_center_y)) || bestview == nullptr)) {
1288 bestview = view;
1289 best_x2 = x2;
1290 best_center_y = center_y;
1291 }
1292 break;
1293 case Right:
1294 if (view != m_view && x1 >= curr_x2
1295 && (x1 < best_x1 || (x1 == best_x1 && qAbs(t: curr_cursor_y - center_y) < qAbs(t: curr_cursor_y - best_center_y)) || bestview == nullptr)) {
1296 bestview = view;
1297 best_x1 = x1;
1298 best_center_y = center_y;
1299 }
1300 break;
1301 case Down:
1302 if (view != m_view && y1 >= curr_y2
1303 && (y1 < best_y1 || (y1 == best_y1 && qAbs(t: curr_cursor_x - center_x) < qAbs(t: curr_cursor_x - best_center_x)) || bestview == nullptr)) {
1304 bestview = view;
1305 best_y1 = y1;
1306 best_center_x = center_x;
1307 }
1308 break;
1309 case Up:
1310 if (view != m_view && y2 <= curr_y1
1311 && (y2 > best_y2 || (y2 == best_y2 && qAbs(t: curr_cursor_x - center_x) < qAbs(t: curr_cursor_x - best_center_x)) || bestview == nullptr)) {
1312 bestview = view;
1313 best_y2 = y2;
1314 best_center_x = center_x;
1315 }
1316 break;
1317 default:
1318 return;
1319 }
1320 }
1321 }
1322 if (bestview != nullptr) {
1323 bestview->setFocus();
1324 bestview->setInputMode(mode: KTextEditor::View::ViInputMode);
1325 }
1326}
1327
1328Range ModeBase::motionFindPrev()
1329{
1330 Searcher *searcher = m_viInputModeManager->searcher();
1331 Range match = searcher->motionFindPrev(count: getCount());
1332 if (searcher->lastSearchWrapped()) {
1333 m_view->showSearchWrappedHint(/*isReverseSearch*/ true);
1334 }
1335
1336 return match;
1337}
1338
1339Range ModeBase::motionFindNext()
1340{
1341 Searcher *searcher = m_viInputModeManager->searcher();
1342 Range match = searcher->motionFindNext(count: getCount());
1343 if (searcher->lastSearchWrapped()) {
1344 m_view->showSearchWrappedHint(/*isReverseSearch*/ false);
1345 }
1346
1347 return match;
1348}
1349
1350void ModeBase::goToPos(const Range &r)
1351{
1352 KTextEditor::Cursor c;
1353 c.setLine(r.endLine);
1354 c.setColumn(r.endColumn);
1355
1356 if (!c.isValid()) {
1357 return;
1358 }
1359
1360 if (r.jump) {
1361 m_viInputModeManager->jumps()->add(cursor: m_view->cursorPosition());
1362 }
1363
1364 if (c.line() >= doc()->lines()) {
1365 c.setLine(doc()->lines() - 1);
1366 }
1367
1368 updateCursor(c);
1369}
1370
1371unsigned int ModeBase::linesDisplayed() const
1372{
1373 return m_viInputModeManager->inputAdapter()->linesDisplayed();
1374}
1375
1376void ModeBase::scrollViewLines(int l)
1377{
1378 m_viInputModeManager->inputAdapter()->scrollViewLines(offset: l);
1379}
1380
1381int ModeBase::getCount() const
1382{
1383 if (m_oneTimeCountOverride != -1) {
1384 return m_oneTimeCountOverride;
1385 }
1386 return (m_count > 0) ? m_count : 1;
1387}
1388

source code of ktexteditor/src/vimode/modes/modebase.cpp