KTextEditor

modebase.cpp
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, 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(r.startLine);
64 }
65 doc()->editEnd();
66 } else {
67 res = doc()->removeText(r.toEditorRange(), 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(UnnamedRegister);
73 if (addToRegister) {
74 fillRegister(chosenRegister, removedText, 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(PrependNumberedRegister, removedText, mode);
81 chosenRegister = PrependNumberedRegister;
82 } else if (chosenRegister == UnnamedRegister) {
83 // only set the SmallDeleteRegister when no register was selected
84 fillRegister(SmallDeleteRegister, removedText, mode);
85 chosenRegister = SmallDeleteRegister;
86 }
87 yankToClipBoard(chosenRegister, 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(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(QLatin1Char('\n'));
109 s.append(QLatin1Char('\n'));
110 } else if (mode == Block) {
111 s = doc()->text(range, 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(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(c.line());
129
130 if (line.length() == 0 && c.column() >= line.length()) {
131 return QChar::Null;
132 }
133
134 return line.at(c.column());
135}
136
137const QString ModeBase::getWordUnderCursor() const
138{
139 return doc()->text(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(c);
148 int i = 0;
149 while (!ch.isLetterOrNumber() && !ch.isMark() && ch != QLatin1Char('_') && m_extraWordCharacters.indexOf(ch) == -1) {
150 // advance cursor one position
151 c.setColumn(c.column() + 1);
152 if (c.column() > doc()->lineLength(c.line())) {
153 c.setColumn(0);
154 c.setLine(c.line() + 1);
155 if (c.line() == doc()->lines()) {
157 }
158 }
159
160 ch = doc()->characterAt(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(c.line(), c.column() + 1 + i, true);
169 KTextEditor::Cursor c2 = findWordEnd(c1.line(), c1.column() + i - 1, 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(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(QLatin1String("|[") + m_extraWordCharacters + QLatin1Char(']'));
183 }
184 startOfWordPattern.append(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(startOfWord, c + 1);
199 int c2 = line.indexOf(nonSpaceAfterSpace, c);
200 int c3 = line.indexOf(nonWordAfterWord, c + 1);
201
202 if (c1 == -1 && c2 == -1 && c3 == -1) {
203 if (onlyCurrentLine) {
205 } else if (l >= doc()->lines() - 1) {
206 c = qMax(line.length() - 1, 0);
208 } else {
209 c = 0;
210 l++;
211
212 line = getLine(l);
213
214 if (line.length() == 0 || !line.at(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(c1, qMin(c2, 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(startOfWORD, 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(l);
266
267 if (line.length() == 0 || !line.at(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(fromLine);
285
286 QString endOfWordPattern = QStringLiteral("\\S\\s|\\S$|\\S\\b|\\w\\W|^$");
287
288 if (m_extraWordCharacters.length() > 0) {
289 endOfWordPattern.append(QLatin1String("|[") + m_extraWordCharacters + QLatin1String("][^") + m_extraWordCharacters + QLatin1Char(']'));
290 }
291
293
294 int l = fromLine;
295 int c = fromColumn;
296
297 bool found = false;
298
299 while (!found) {
300 int c1 = line.lastIndexOf(endOfWord, c - 1);
301
302 if (c1 != -1 && c - 1 != -1) {
303 found = true;
304 c = c1;
305 } else {
306 if (onlyCurrentLine) {
308 } else if (l > 0) {
309 line = getLine(--l);
310 c = line.length();
311
312 continue;
313 } else {
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(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(endOfWORDPattern, c - 1);
335
336 if (c1 != -1 && c - 1 != -1) {
337 found = true;
338 c = c1;
339 } else {
340 if (onlyCurrentLine) {
342 } else if (l > 0) {
343 line = getLine(--l);
344 c = line.length();
345
346 continue;
347 } else {
348 c = 0;
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(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(QLatin1String("|[") + m_extraWordCharacters + QLatin1Char(']'));
365 }
366 startOfWordPattern.append(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(startOfWord, c - 1) : -1;
382 int c2 = (c > 1) ? line.lastIndexOf(nonSpaceAfterSpace, c - 2) : -1;
383 int c3 = (c > 0) ? line.lastIndexOf(nonWordAfterWord, c - 1) : -1;
384 int c4 = (c > 0) ? line.lastIndexOf(startOfLine, c - 1) : -1;
385
386 if (c1 == -1 && c2 == -1 && c3 == -1 && c4 == -1) {
387 if (onlyCurrentLine) {
389 } else if (l <= 0) {
391 } else {
392 line = getLine(--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(c1, qMax(c2, qMax(c3, 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(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(startOfWORD, c - 2) : -1;
441 int c2 = (c > 0) ? line.lastIndexOf(startOfLineWORD, c - 1) : -1;
442
443 if (c1 == -1 && c2 == -1) {
444 if (onlyCurrentLine) {
446 } else if (l <= 0) {
448 } else {
449 line = getLine(--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(c1, 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(fromLine);
478
479 QString endOfWordPattern = QStringLiteral("\\S\\s|\\S$|\\w\\W|\\S\\b");
480
481 if (m_extraWordCharacters.length() > 0) {
482 endOfWordPattern.append(QLatin1String("|[") + m_extraWordCharacters + QLatin1String("][^") + m_extraWordCharacters + QLatin1Char(']'));
483 }
484
486
487 int l = fromLine;
488 int c = fromColumn;
489
490 bool found = false;
491
492 while (!found) {
493 int c1 = line.indexOf(endOfWORD, c + 1);
494
495 if (c1 != -1) {
496 found = true;
497 c = c1;
498 } else {
499 if (onlyCurrentLine) {
501 } else if (l >= doc()->lines() - 1) {
502 c = line.length() - 1;
504 } else {
505 c = -1;
506 line = getLine(++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(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(endOfWORD, c + 1);
529
530 if (c1 != -1) {
531 found = true;
532 c = c1;
533 } else {
534 if (onlyCurrentLine) {
536 } else if (l >= doc()->lines() - 1) {
537 c = line.length() - 1;
539 } else {
540 c = -1;
541 line = getLine(++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(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(cursor.line());
576
577 // If cursor on the quote we should choose the best direction.
578 if (line.at(cursor.column()) == c) {
579 int attribute = m_view->doc()->kateTextLine(cursor.line()).attribute(cursor.column());
580
581 // If at the beginning of the line - then we might search the end.
582 if (doc()->kateTextLine(cursor.line()).attribute(cursor.column() + 1) == attribute
583 && doc()->kateTextLine(cursor.line()).attribute(cursor.column() - 1) != attribute) {
584 r.startColumn = cursor.column();
585 r.endColumn = line.indexOf(c, cursor.column() + 1);
586
587 return innerRange(r, inner);
588 }
589
590 // If at the end of the line - then we might search the beginning.
591 if (doc()->kateTextLine(cursor.line()).attribute(cursor.column() + 1) != attribute
592 && doc()->kateTextLine(cursor.line()).attribute(cursor.column() - 1) == attribute) {
593 r.startColumn = line.lastIndexOf(c, cursor.column() - 1);
594 r.endColumn = cursor.column();
595
596 return innerRange(r, inner);
597 }
598 // Try to search the quote to right
599 int c1 = line.indexOf(c, cursor.column() + 1);
600 if (c1 != -1) {
601 r.startColumn = cursor.column();
602 r.endColumn = c1;
603
604 return innerRange(r, inner);
605 }
606
607 // Try to search the quote to left
608 int c2 = line.lastIndexOf(c, cursor.column() - 1);
609 if (c2 != -1) {
610 r.startColumn = c2;
611 r.endColumn = cursor.column();
612
613 return innerRange(r, inner);
614 }
615
616 // Nothing found - give up :)
617 return Range::invalid();
618 }
619
620 r.startColumn = line.lastIndexOf(c, cursor.column());
621 r.endColumn = line.indexOf(c, cursor.column());
622
623 if (r.startColumn == -1 || r.endColumn == -1 || r.startColumn > r.endColumn) {
624 return Range::invalid();
625 }
626
627 return innerRange(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(column) == c2) {
643 r.endLine = line;
644 r.endColumn = column;
645 } else {
646 if (column < l.size() && l.at(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(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(column) == c1) {
683 r.startLine = line;
684 r.startColumn = column;
685 } else {
686 if (column < l.size() && l.at(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(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 - 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(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(c1, cursor.column());
726 int col2 = line.indexOf(c2, 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(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(getCount());
822}
823
824Range ModeBase::goLineUp()
825{
826 return goLineUpDown(-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(c.line());
854 Kate::TextLine endLine = doc()->plainKateTextLine(r.endLine);
855
856 int endLineLen = doc()->lineLength(r.endLine) - 1;
857
858 if (endLineLen < 0) {
859 endLineLen = 0;
860 }
861
862 int endLineLenVirt = endLine.toVirtualColumn(endLineLen, tabstop);
863 int virtColumnStart = startLine.toVirtualColumn(c.column(), 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(virtColumnStart, tabstop);
868 m_stickyColumn = virtColumnStart;
869 } else {
870 // sticky is set - set end column to its value
871 r.endColumn = endLine.fromVirtualColumn(m_stickyColumn, 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(m_view->cursorPosition());
903 int finishRealLine = m_view->cursorPosition().line();
904 int count = qAbs(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(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(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(m_view->cursorPosition());
954 int startRealLine = m_view->cursorPosition().line();
955 const Kate::TextLine startLine = doc()->plainKateTextLine(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(startRealLine, startVisualLine).lineLayout().lineNumber() != 0);
959 const int numInvisibleIndentChars = [&] {
960 if (isWrappedContinuation) {
961 auto l = doc()->plainKateTextLine(startRealLine);
962 return startLine.toVirtualColumn(l.nextNonSpaceChar(0), tabstop);
963 }
964 return 0;
965 }();
966
967 const int realLineStartColumn = cache->textLayout(startRealLine, startVisualLine).startCol();
968 const int lineStartVirtualColumn = startLine.toVirtualColumn(realLineStartColumn, tabstop);
969 const int visualColumnNoInvisibleIndent = startLine.toVirtualColumn(c.column(), 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(finishRealLine, finishVisualLine).startCol();
977 const Kate::TextLine endLine = doc()->plainKateTextLine(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(finishRealLine, finishVisualLine).lineLayout().lineNumber() != 0);
981 const int numInvisibleIndentChars = [&] {
982 if (isWrappedContinuation) {
983 auto l = doc()->plainKateTextLine(finishRealLine);
984 return endLine.toVirtualColumn(l.nextNonSpaceChar(0), tabstop);
985 }
986 return 0;
987 }();
988 if (m_stickyColumn == (unsigned int)KateVi::EOL) {
989 const int visualEndColumn = cache->textLayout(finishRealLine, finishVisualLine).lineLayout().textLength() - 1;
990 r.endColumn = endLine.fromVirtualColumn(visualEndColumn + realLineStartColumn - numInvisibleIndentChars, 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(realLineStartColumn, tabstop);
996 while (true) {
997 const int visualColumn =
998 endLine.toVirtualColumn(realLineStartColumn + realOffsetToVisualStickyColumn, 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(m_view, 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(m_view, 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(m_view, 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(ViMode::VisualMode);
1052 } else if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualBlockMode) {
1053 m_viInputModeManager->getViVisualMode()->setVisualModeType(ViMode::VisualMode);
1054 m_viInputModeManager->changeViMode(ViMode::VisualMode);
1055 } else {
1056 m_viInputModeManager->viEnterVisualMode();
1057 }
1058
1059 Q_EMIT m_view->viewModeChanged(m_view, m_view->viewMode());
1060
1061 return true;
1062}
1063
1064bool ModeBase::startVisualBlockMode()
1065{
1066 if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualMode) {
1067 m_viInputModeManager->getViVisualMode()->setVisualModeType(ViMode::VisualBlockMode);
1068 m_viInputModeManager->changeViMode(ViMode::VisualBlockMode);
1069 } else {
1070 m_viInputModeManager->viEnterVisualMode(ViMode::VisualBlockMode);
1071 }
1072
1073 Q_EMIT m_view->viewModeChanged(m_view, m_view->viewMode());
1074
1075 return true;
1076}
1077
1078bool ModeBase::startVisualLineMode()
1079{
1080 if (m_viInputModeManager->getCurrentViMode() == ViMode::VisualMode) {
1081 m_viInputModeManager->getViVisualMode()->setVisualModeType(ViMode::VisualLineMode);
1082 m_viInputModeManager->changeViMode(ViMode::VisualLineMode);
1083 } else {
1084 m_viInputModeManager->viEnterVisualMode(ViMode::VisualLineMode);
1085 }
1086
1087 Q_EMIT m_view->viewModeChanged(m_view, m_view->viewMode());
1088
1089 return true;
1090}
1091
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(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(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(column) == QLatin1Char('\t')) {
1133 tempCol += tabWidth - (tempCol % tabWidth);
1134 } else {
1135 tempCol++;
1136 }
1137
1138 if (tempCol <= virtualColumn) {
1139 column++;
1140
1141 if (column >= line.length()) {
1142 return QChar::Null;
1143 }
1144 }
1145 }
1146
1147 if (line.length() > column) {
1148 return line.at(column);
1149 }
1150
1151 return QChar::Null;
1152}
1153
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(cursorLine, cursorColumn);
1166 int wordStartPos = prevWordStart.column();
1167 if (prevWordStart.line() < cursorLine) {
1168 // The previous word starts on the previous line: ignore.
1169 wordStartPos = 0;
1170 }
1171 if (wordStartPos > 0 && line.at(wordStartPos - 1) == QLatin1Char('-')) {
1172 wordStartPos--;
1173 }
1174
1175 int numberStartPos = -1;
1176 QString numberAsString;
1177 static const QRegularExpression numberRegex(QStringLiteral("0x[0-9a-fA-F]+|\\-?\\d+"));
1178 auto numberMatchIter = numberRegex.globalMatch(line, wordStartPos);
1179 while (numberMatchIter.hasNext()) {
1180 const auto numberMatch = numberMatchIter.next();
1181 const bool numberEndedBeforeCursor = (numberMatch.capturedStart() + numberMatch.capturedLength() <= cursorColumn);
1182 if (!numberEndedBeforeCursor) {
1183 // This is the first number-like string under or after the cursor - this'll do!
1184 numberStartPos = numberMatch.capturedStart();
1185 numberAsString = numberMatch.captured();
1186 break;
1187 }
1188 }
1189
1190 if (numberStartPos == -1) {
1191 // None found.
1192 return;
1193 }
1194
1195 bool parsedNumberSuccessfully = false;
1196 int base = numberAsString.startsWith(QLatin1String("0x")) ? 16 : 10;
1197 if (base != 16 && numberAsString.startsWith(QLatin1Char('0')) && numberAsString.length() > 1) {
1198 // If a non-hex number with a leading 0 can be parsed as octal, then assume
1199 // it is octal.
1200 numberAsString.toInt(&parsedNumberSuccessfully, 8);
1201 if (parsedNumberSuccessfully) {
1202 base = 8;
1203 }
1204 }
1205 const int originalNumber = numberAsString.toInt(&parsedNumberSuccessfully, base);
1206
1207 if (!parsedNumberSuccessfully) {
1208 // conversion to int failed. give up.
1209 return;
1210 }
1211
1212 QString basePrefix;
1213 if (base == 16) {
1214 basePrefix = QStringLiteral("0x");
1215 } else if (base == 8) {
1216 basePrefix = QStringLiteral("0");
1217 }
1218
1219 const int withoutBaseLength = numberAsString.length() - basePrefix.length();
1220
1221 const int newNumber = originalNumber + count;
1222
1223 // Create the new text string to be inserted. Prepend with “0x” if in base 16, and "0" if base 8.
1224 // For non-decimal numbers, try to keep the length of the number the same (including leading 0's).
1225 const QString newNumberPadded =
1226 (base == 10) ? QStringLiteral("%1").arg(newNumber, 0, base) : QStringLiteral("%1").arg(newNumber, withoutBaseLength, base, QLatin1Char('0'));
1227 const QString newNumberText = basePrefix + newNumberPadded;
1228
1229 // Replace the old number string with the new.
1230 doc()->editStart();
1231 doc()->removeText(KTextEditor::Range(cursorLine, numberStartPos, cursorLine, numberStartPos + numberAsString.length()));
1232 doc()->insertText(KTextEditor::Cursor(cursorLine, numberStartPos), newNumberText);
1233 doc()->editEnd();
1234 updateCursor(KTextEditor::Cursor(m_view->cursorPosition().line(), numberStartPos + newNumberText.length() - 1));
1235}
1236
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(view);
1243 }
1244 }
1245
1246 QPoint current_point = m_view->mapToGlobal(m_view->pos());
1247 int curr_x1 = current_point.x();
1248 int curr_x2 = current_point.x() + m_view->width();
1249 int curr_y1 = current_point.y();
1250 int curr_y2 = current_point.y() + m_view->height();
1251 const KTextEditor::Cursor cursorPos = m_view->cursorPosition();
1252 const QPoint globalPos = m_view->mapToGlobal(m_view->cursorToCoordinate(cursorPos));
1253 int curr_cursor_y = globalPos.y();
1254 int curr_cursor_x = globalPos.x();
1255
1256 KTextEditor::ViewPrivate *bestview = nullptr;
1257 int best_x1 = -1;
1258 int best_x2 = -1;
1259 int best_y1 = -1;
1260 int best_y2 = -1;
1261 int best_center_y = -1;
1262 int best_center_x = -1;
1263
1264 if (direction == Next && visible_views.size() != 1) {
1265 for (size_t i = 0; i < visible_views.size(); i++) {
1266 if (visible_views.at(i) == m_view) {
1267 if (i != visible_views.size() - 1) {
1268 bestview = visible_views.at(i + 1);
1269 } else {
1270 bestview = visible_views.at(0);
1271 }
1272 }
1273 }
1274 } else {
1275 for (KTextEditor::ViewPrivate *view : visible_views) {
1276 QPoint point = view->mapToGlobal(view->pos());
1277 int x1 = point.x();
1278 int x2 = point.x() + view->width();
1279 int y1 = point.y();
1280 int y2 = point.y() + m_view->height();
1281 int center_y = (y1 + y2) / 2;
1282 int center_x = (x1 + x2) / 2;
1283
1284 switch (direction) {
1285 case Left:
1286 if (view != m_view && x2 <= curr_x1
1287 && (x2 > best_x2 || (x2 == best_x2 && qAbs(curr_cursor_y - center_y) < qAbs(curr_cursor_y - best_center_y)) || bestview == nullptr)) {
1288 bestview = view;
1289 best_x2 = x2;
1290 best_center_y = center_y;
1291 }
1292 break;
1293 case Right:
1294 if (view != m_view && x1 >= curr_x2
1295 && (x1 < best_x1 || (x1 == best_x1 && qAbs(curr_cursor_y - center_y) < qAbs(curr_cursor_y - best_center_y)) || bestview == nullptr)) {
1296 bestview = view;
1297 best_x1 = x1;
1298 best_center_y = center_y;
1299 }
1300 break;
1301 case Down:
1302 if (view != m_view && y1 >= curr_y2
1303 && (y1 < best_y1 || (y1 == best_y1 && qAbs(curr_cursor_x - center_x) < qAbs(curr_cursor_x - best_center_x)) || bestview == nullptr)) {
1304 bestview = view;
1305 best_y1 = y1;
1306 best_center_x = center_x;
1307 }
1308 break;
1309 case Up:
1310 if (view != m_view && y2 <= curr_y1
1311 && (y2 > best_y2 || (y2 == best_y2 && qAbs(curr_cursor_x - center_x) < qAbs(curr_cursor_x - best_center_x)) || bestview == nullptr)) {
1312 bestview = view;
1313 best_y2 = y2;
1314 best_center_x = center_x;
1315 }
1316 break;
1317 default:
1318 return;
1319 }
1320 }
1321 }
1322 if (bestview != nullptr) {
1323 bestview->setFocus();
1324 bestview->setInputMode(KTextEditor::View::ViInputMode);
1325 }
1326}
1327
1328Range ModeBase::motionFindPrev()
1329{
1330 Searcher *searcher = m_viInputModeManager->searcher();
1331 Range match = searcher->motionFindPrev(getCount());
1332 if (searcher->lastSearchWrapped()) {
1333 m_view->showSearchWrappedHint(/*isReverseSearch*/ true);
1334 }
1335
1336 return match;
1337}
1338
1339Range ModeBase::motionFindNext()
1340{
1341 Searcher *searcher = m_viInputModeManager->searcher();
1342 Range match = searcher->motionFindNext(getCount());
1343 if (searcher->lastSearchWrapped()) {
1344 m_view->showSearchWrappedHint(/*isReverseSearch*/ false);
1345 }
1346
1347 return match;
1348}
1349
1350void ModeBase::goToPos(const Range &r)
1351{
1353 c.setLine(r.endLine);
1354 c.setColumn(r.endColumn);
1355
1356 if (!c.isValid()) {
1357 return;
1358 }
1359
1360 if (r.jump) {
1361 m_viInputModeManager->jumps()->add(m_view->cursorPosition());
1362 }
1363
1364 if (c.line() >= doc()->lines()) {
1365 c.setLine(doc()->lines() - 1);
1366 }
1367
1368 updateCursor(c);
1369}
1370
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(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}
The Cursor represents a position in a Document.
Definition cursor.h:75
constexpr int column() const noexcept
Retrieve the column on which this cursor is situated.
Definition cursor.h:192
void setColumn(int column) noexcept
Set the cursor column to column.
Definition cursor.h:201
constexpr bool isValid() const noexcept
Returns whether the current position of this cursor is a valid position (line + column must both be >...
Definition cursor.h:102
void setLine(int line) noexcept
Set the cursor line to line.
Definition cursor.h:183
constexpr int line() const noexcept
Retrieve the line on which this cursor is situated.
Definition cursor.h:174
static constexpr Cursor invalid() noexcept
Returns an invalid cursor.
Definition cursor.h:112
void copyToClipboard(const QString &text, const QString &fileName)
Copy text to clipboard an remember it in the history.
static KTextEditor::EditorPrivate * self()
Kate Part Internal stuff ;)
This class holds a Message to display in Views.
Definition message.h:94
@ BottomInView
show message as view overlay in the bottom right corner.
Definition message.h:125
@ Error
error message type
Definition message.h:110
@ Positive
positive information message
Definition message.h:107
An object representing a section of text, from one Cursor to another.
static constexpr Range invalid() noexcept
Returns an invalid range.
@ ViInputMode
Vi mode.
Definition view.h:288
void viewModeChanged(KTextEditor::View *view, KTextEditor::View::ViewMode mode)
This signal is emitted whenever the view mode of view changes.
This class handles Kate's caching of layouting information (in KateLineLayout and KateTextLayout).
KateTextLayout & viewLine(int viewLine)
Returns the layout of the corresponding line in the view.
KateLineLayout * line(int realLine, int virtualLine=-1)
Returns the KateLineLayout for the specified line.
KateTextLayout textLayout(const KTextEditor::Cursor realCursor)
Returns the layout describing the text line which is occupied by realCursor.
Class representing a single text line.
int toVirtualColumn(int column, int tabWidth) const
Returns the column with each tab expanded into tabWidth characters.
int fromVirtualColumn(int column, int tabWidth) const
Returns the "real" column where each tab only counts one character.
QString i18n(const char *text, const TYPE &arg...)
KCOREADDONS_EXPORT Result match(QStringView pattern, QStringView str)
The KTextEditor namespace contains all the public API that is required to use the KTextEditor compone...
bool isLetterOrNumber(char32_t ucs4)
bool isMark(char32_t ucs4)
bool isSpace(char32_t ucs4)
char32_t toLower(char32_t ucs4)
Q_EMITQ_EMIT
int x() const const
int y() const const
QString & append(QChar ch)
QString arg(Args &&... args) const const
const QChar at(qsizetype position) const const
QChar & back()
qsizetype indexOf(QChar ch, qsizetype from, Qt::CaseSensitivity cs) const const
bool isEmpty() const const
bool isNull() const const
qsizetype lastIndexOf(QChar ch, Qt::CaseSensitivity cs) const const
qsizetype length() const const
qsizetype size() const const
bool startsWith(QChar c, Qt::CaseSensitivity cs) const const
int toInt(bool *ok, int base) const const
QString trimmed() const const
int lineNumber() const const
int textLength() const const
QPoint mapToGlobal(const QPoint &pos) const const
void setFocus()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jun 14 2024 11:57:26 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.