KTextEditor

katetextfolding.cpp
1/*
2 SPDX-FileCopyrightText: 2013 Christoph Cullmann <cullmann@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "katetextfolding.h"
8#include "katedocument.h"
9#include "katetextbuffer.h"
10#include "katetextrange.h"
11
12#include <QJsonObject>
13
14namespace Kate
15{
16TextFolding::FoldingRange::FoldingRange(TextBuffer &buffer, KTextEditor::Range range, FoldingRangeFlags _flags)
17 : start(new TextCursor(&buffer, range.start(), KTextEditor::MovingCursor::MoveOnInsert))
18 , end(new TextCursor(&buffer, range.end(), KTextEditor::MovingCursor::MoveOnInsert))
19 , parent(nullptr)
20 , flags(_flags)
21 , id(-1)
22{
23}
24
25TextFolding::FoldingRange::~FoldingRange()
26{
27 // kill all our data!
28 // this will recurse all sub-structures!
29 delete start;
30 delete end;
31 qDeleteAll(nestedRanges);
32}
33
34TextFolding::TextFolding(TextBuffer &buffer)
35 : QObject()
36 , m_buffer(buffer)
37 , m_idCounter(-1)
38{
39 // connect needed signals from buffer
41}
42
44{
45 // only delete the folding ranges, the folded ranges and mapped ranges are the same objects
46 qDeleteAll(m_foldingRanges);
47}
48
50{
51 // reset counter
52 m_idCounter = -1;
53 clearFoldingRanges();
54}
55
56void TextFolding::clearFoldingRanges()
57{
58 // no ranges, no work
59 if (m_foldingRanges.isEmpty()) {
60 // assert all stuff is consistent and return!
61 Q_ASSERT(m_idToFoldingRange.isEmpty());
62 Q_ASSERT(m_foldedFoldingRanges.isEmpty());
63 return;
64 }
65
66 // cleanup
67 m_idToFoldingRange.clear();
68 m_foldedFoldingRanges.clear();
69 qDeleteAll(m_foldingRanges);
70 m_foldingRanges.clear();
71
72 // folding changed!
74}
75
77{
78 // sort out invalid and empty ranges
79 // that makes no sense, they will never grow again!
80 if (!range.isValid() || range.isEmpty()) {
81 return -1;
82 }
83
84 // create new folding region that we want to insert
85 // this will internally create moving cursors!
86 FoldingRange *newRange = new FoldingRange(m_buffer, range, flags);
87
88 // the construction of the text cursors might have invalidated this
89 // check and bail out if that happens
90 // bail out, too, if it can't be inserted!
91 if (!newRange->start->isValid() || !newRange->end->isValid() || !insertNewFoldingRange(nullptr /* no parent here */, m_foldingRanges, newRange)) {
92 // cleanup and be done
93 delete newRange;
94 return -1;
95 }
96
97 // set id, catch overflows, even if they shall not happen
98 newRange->id = ++m_idCounter;
99 if (newRange->id < 0) {
100 newRange->id = m_idCounter = 0;
101 }
102
103 // remember the range
104 m_idToFoldingRange.insert(newRange->id, newRange);
105
106 // update our folded ranges vector!
107 bool updated = updateFoldedRangesForNewRange(newRange);
108
109 // emit that something may have changed
110 // do that only, if updateFoldedRangesForNewRange did not already do the job!
111 if (!updated) {
113 }
114
115 // all went fine, newRange is now registered internally!
116 return newRange->id;
117}
118
120{
121 FoldingRange *range = m_idToFoldingRange.value(id);
122 if (!range) {
124 }
125
126 return KTextEditor::Range(range->start->toCursor(), range->end->toCursor());
127}
128
130{
131 // try to find the range, else bail out
132 FoldingRange *range = m_idToFoldingRange.value(id);
133 if (!range) {
134 return false;
135 }
136
137 // already folded? nothing to do
138 if (range->flags & Folded) {
139 return true;
140 }
141
142 // fold and be done
143 range->flags |= Folded;
144 updateFoldedRangesForNewRange(range);
145 return true;
146}
147
148bool TextFolding::unfoldRange(qint64 id, bool remove)
149{
150 // try to find the range, else bail out
151 FoldingRange *range = m_idToFoldingRange.value(id);
152 if (!range) {
153 return false;
154 }
155
156 // nothing to do?
157 // range is already unfolded and we need not to remove it!
158 if (!remove && !(range->flags & Folded)) {
159 return true;
160 }
161
162 // do we need to delete the range?
163 const bool deleteRange = remove || !(range->flags & Persistent);
164
165 // first: remove the range, if forced or non-persistent!
166 if (deleteRange) {
167 // remove from outside visible mapping!
168 m_idToFoldingRange.remove(id);
169
170 // remove from folding vectors!
171 // FIXME: OPTIMIZE
172 FoldingRange::Vector &parentVector = range->parent ? range->parent->nestedRanges : m_foldingRanges;
173 FoldingRange::Vector newParentVector;
174 for (FoldingRange *curRange : std::as_const(parentVector)) {
175 // insert our nested ranges and reparent them
176 if (curRange == range) {
177 for (FoldingRange *newRange : std::as_const(range->nestedRanges)) {
178 newRange->parent = range->parent;
179 newParentVector.push_back(newRange);
180 }
181
182 continue;
183 }
184
185 // else just transfer elements
186 newParentVector.push_back(curRange);
187 }
188 parentVector = newParentVector;
189 }
190
191 // second: unfold the range, if needed!
192 bool updated = false;
193 if (range->flags & Folded) {
194 range->flags &= ~Folded;
195 updated = updateFoldedRangesForRemovedRange(range);
196 }
197
198 // emit that something may have changed
199 // do that only, if updateFoldedRangesForRemoveRange did not already do the job!
200 if (!updated) {
202 }
203
204 // really delete the range, if needed!
205 if (deleteRange) {
206 // clear ranges first, they got moved!
207 range->nestedRanges.clear();
208 delete range;
209 }
210
211 // be done ;)
212 return true;
213}
214
215bool TextFolding::isLineVisible(int line, qint64 *foldedRangeId) const
216{
217 // skip if nothing folded
218 if (m_foldedFoldingRanges.isEmpty()) {
219 return true;
220 }
221
222 // search upper bound, index to item with start line higher than our one
223 FoldingRange::Vector::const_iterator upperBound =
224 std::upper_bound(m_foldedFoldingRanges.begin(), m_foldedFoldingRanges.end(), line, compareRangeByStartWithLine);
225 if (upperBound != m_foldedFoldingRanges.begin()) {
226 --upperBound;
227 }
228
229 // check if we overlap with the range in front of us
230 const bool hidden = (((*upperBound)->end->line() >= line) && (line > (*upperBound)->start->line()));
231
232 // fill in folded range id, if needed
233 if (foldedRangeId) {
234 (*foldedRangeId) = hidden ? (*upperBound)->id : -1;
235 }
236
237 // visible == !hidden
238 return !hidden;
239}
240
242{
243 // skip if nothing folded
244 if (m_foldedFoldingRanges.isEmpty()) {
245 return;
246 }
247
248 // while not visible, unfold
249 qint64 foldedRangeId = -1;
250 while (!isLineVisible(line, &foldedRangeId)) {
251 // id should be valid!
252 Q_ASSERT(foldedRangeId >= 0);
253
254 // unfold shall work!
255 const bool unfolded = unfoldRange(foldedRangeId);
256 (void)unfolded;
257 Q_ASSERT(unfolded);
258 }
259}
260
262{
263 // start with all lines we have
264 int visibleLines = m_buffer.lines();
265
266 // skip if nothing folded
267 if (m_foldedFoldingRanges.isEmpty()) {
268 return visibleLines;
269 }
270
271 // count all folded lines and subtract them from visible lines
272 for (FoldingRange *range : m_foldedFoldingRanges) {
273 visibleLines -= (range->end->line() - range->start->line());
274 }
275
276 // be done, assert we did no trash
277 Q_ASSERT(visibleLines > 0);
278 return visibleLines;
279}
280
282{
283 // valid input needed!
284 Q_ASSERT(line >= 0);
285
286 // start with identity
287 int visibleLine = line;
288
289 // skip if nothing folded or first line
290 if (m_foldedFoldingRanges.isEmpty() || (line == 0)) {
291 return visibleLine;
292 }
293
294 // walk over all folded ranges until we reach the line
295 // keep track of seen visible lines, for the case we want to convert a hidden line!
296 int seenVisibleLines = 0;
297 int lastLine = 0;
298 for (FoldingRange *range : m_foldedFoldingRanges) {
299 // abort if we reach our line!
300 if (range->start->line() >= line) {
301 break;
302 }
303
304 // count visible lines
305 seenVisibleLines += (range->start->line() - lastLine);
306 lastLine = range->end->line();
307
308 // we might be contained in the region, then we return last visible line
309 if (line <= range->end->line()) {
310 return seenVisibleLines;
311 }
312
313 // subtrace folded lines
314 visibleLine -= (range->end->line() - range->start->line());
315 }
316
317 // be done, assert we did no trash
318 Q_ASSERT(visibleLine >= 0);
319 return visibleLine;
320}
321
322int TextFolding::visibleLineToLine(int visibleLine) const
323{
324 // valid input needed!
325 Q_ASSERT(visibleLine >= 0);
326
327 // start with identity
328 int line = visibleLine;
329
330 // skip if nothing folded or first line
331 if (m_foldedFoldingRanges.isEmpty() || (visibleLine == 0)) {
332 return line;
333 }
334
335 // last visible line seen, as line in buffer
336 int seenVisibleLines = 0;
337 int lastLine = 0;
338 int lastLineVisibleLines = 0;
339 for (FoldingRange *range : m_foldedFoldingRanges) {
340 // else compute visible lines and move last seen
341 lastLineVisibleLines = seenVisibleLines;
342 seenVisibleLines += (range->start->line() - lastLine);
343
344 // bail out if enough seen
345 if (seenVisibleLines >= visibleLine) {
346 break;
347 }
348
349 lastLine = range->end->line();
350 }
351
352 // check if still no enough visible!
353 if (seenVisibleLines < visibleLine) {
354 lastLineVisibleLines = seenVisibleLines;
355 }
356
357 // compute visible line
358 line = (lastLine + (visibleLine - lastLineVisibleLines));
359 Q_ASSERT(line >= 0);
360 return line;
361}
362
364{
365 // results vector
367
368 // recursively do binary search
369 foldingRangesStartingOnLine(results, m_foldingRanges, line);
370
371 // return found results
372 return results;
373}
374
375void TextFolding::foldingRangesStartingOnLine(QList<QPair<qint64, FoldingRangeFlags>> &results, const TextFolding::FoldingRange::Vector &ranges, int line) const
376{
377 // early out for no folds
378 if (ranges.isEmpty()) {
379 return;
380 }
381
382 // first: lower bound of start
383 FoldingRange::Vector::const_iterator lowerBound = std::lower_bound(ranges.begin(), ranges.end(), line, compareRangeByLineWithStart);
384
385 // second: upper bound of end
386 FoldingRange::Vector::const_iterator upperBound = std::upper_bound(ranges.begin(), ranges.end(), line, compareRangeByStartWithLine);
387
388 // we may need to go one to the left, if not already at the begin, as we might overlap with the one in front of us!
389 if ((lowerBound != ranges.begin()) && ((*(lowerBound - 1))->end->line() >= line)) {
390 --lowerBound;
391 }
392
393 // for all of them, check if we start at right line and recurse
394 for (FoldingRange::Vector::const_iterator it = lowerBound; it != upperBound; ++it) {
395 // this range already ok? add it to results
396 if ((*it)->start->line() == line) {
397 results.push_back(qMakePair((*it)->id, (*it)->flags));
398 }
399
400 // recurse anyway
401 foldingRangesStartingOnLine(results, (*it)->nestedRanges, line);
402 }
403}
404
406{
407 // toplevel ranges requested or real parent?
408 const FoldingRange::Vector *ranges = nullptr;
409 if (parentRangeId == -1) {
410 ranges = &m_foldingRanges;
411 } else if (FoldingRange *range = m_idToFoldingRange.value(parentRangeId)) {
412 ranges = &range->nestedRanges;
413 }
414
415 // no ranges => nothing to do
417 if (!ranges) {
418 return results;
419 }
420
421 // else convert ranges to id + flags and pass that back
422 for (FoldingRange::Vector::const_iterator it = ranges->begin(); it != ranges->end(); ++it) {
423 results.append(qMakePair((*it)->id, (*it)->flags));
424 }
425 return results;
426}
427
429{
430 // dump toplevel ranges recursively
431 return QStringLiteral("tree %1 - folded %2").arg(debugDump(m_foldingRanges, true), debugDump(m_foldedFoldingRanges, false));
432}
433
434void TextFolding::debugPrint(const QString &title) const
435{
436 // print title + content
437 printf("%s\n %s\n", qPrintable(title), qPrintable(debugDump()));
438}
439
440void TextFolding::editEnd(int startLine, int endLine, std::function<bool(int)> isLineFoldingStart)
441{
442 // search upper bound, index to item with start line higher than our one
443 auto foldIt = std::upper_bound(m_foldedFoldingRanges.begin(), m_foldedFoldingRanges.end(), startLine, compareRangeByStartWithLine);
444 if (foldIt != m_foldedFoldingRanges.begin()) {
445 --foldIt;
446 }
447
448 // handle all ranges until we go behind the last line
449 bool anyUpdate = false;
450 while (foldIt != m_foldedFoldingRanges.end() && (*foldIt)->start->line() <= endLine) {
451 // shall we keep this folding?
452 if (isLineFoldingStart((*foldIt)->start->line())) {
453 ++foldIt;
454 continue;
455 }
456
457 // else kill it
458 m_foldingRanges.removeOne(*foldIt);
459 m_idToFoldingRange.remove((*foldIt)->id);
460 delete *foldIt;
461 foldIt = m_foldedFoldingRanges.erase(foldIt);
462 anyUpdate = true;
463 }
464
465 // ensure we do the proper updates outside
466 // we might change some range outside of the lines we edited
467 if (anyUpdate) {
469 }
470}
471
472QString TextFolding::debugDump(const TextFolding::FoldingRange::Vector &ranges, bool recurse)
473{
474 // dump all ranges recursively
475 QString dump;
476 for (FoldingRange *range : ranges) {
477 if (!dump.isEmpty()) {
478 dump += QLatin1Char(' ');
479 }
480
481 const QString persistent = (range->flags & Persistent) ? QStringLiteral("p") : QString();
482 const QString folded = (range->flags & Folded) ? QStringLiteral("f") : QString();
483 dump += QStringLiteral("[%1:%2 %3%4 ").arg(range->start->line()).arg(range->start->column()).arg(persistent, folded);
484
485 // recurse
486 if (recurse) {
487 QString inner = debugDump(range->nestedRanges, recurse);
488 if (!inner.isEmpty()) {
489 dump += inner + QLatin1Char(' ');
490 }
491 }
492
493 dump += QStringLiteral("%1:%2]").arg(range->end->line()).arg(range->end->column());
494 }
495 return dump;
496}
497
498bool TextFolding::insertNewFoldingRange(FoldingRange *parent, FoldingRange::Vector &existingRanges, FoldingRange *newRange)
499{
500 // existing ranges are non-overlapping and sorted
501 // that means, we can search for lower bound of start of range and upper bound of end of range to find all "overlapping" ranges.
502
503 // first: lower bound of start
504 FoldingRange::Vector::iterator lowerBound = std::lower_bound(existingRanges.begin(), existingRanges.end(), newRange, compareRangeByStart);
505
506 // second: upper bound of end
507 FoldingRange::Vector::iterator upperBound = std::upper_bound(existingRanges.begin(), existingRanges.end(), newRange, compareRangeByEnd);
508
509 // we may need to go one to the left, if not already at the begin, as we might overlap with the one in front of us!
510 if ((lowerBound != existingRanges.begin()) && ((*(lowerBound - 1))->end->toCursor() > newRange->start->toCursor())) {
511 --lowerBound;
512 }
513
514 // now: first case, we overlap with nothing or hit exactly one range!
515 if (lowerBound == upperBound) {
516 // nothing we overlap with?
517 // then just insert and be done!
518 if ((lowerBound == existingRanges.end()) || (newRange->start->toCursor() >= (*lowerBound)->end->toCursor())
519 || (newRange->end->toCursor() <= (*lowerBound)->start->toCursor())) {
520 // insert + fix parent
521 existingRanges.insert(lowerBound, newRange);
522 newRange->parent = parent;
523
524 // all done
525 return true;
526 }
527
528 // we are contained in this one range?
529 // then recurse!
530 if ((newRange->start->toCursor() >= (*lowerBound)->start->toCursor()) && (newRange->end->toCursor() <= (*lowerBound)->end->toCursor())) {
531 return insertNewFoldingRange((*lowerBound), (*lowerBound)->nestedRanges, newRange);
532 }
533
534 // else: we might contain at least this fold, or many more, if this if block is not taken at all
535 // use the general code that checks for "we contain stuff" below!
536 }
537
538 // check if we contain other folds!
539 FoldingRange::Vector::iterator it = lowerBound;
540 bool includeUpperBound = false;
541 FoldingRange::Vector nestedRanges;
542 while (it != existingRanges.end()) {
543 // do we need to take look at upper bound, too?
544 // if not break
545 if (it == upperBound) {
546 if (newRange->end->toCursor() <= (*upperBound)->start->toCursor()) {
547 break;
548 } else {
549 includeUpperBound = true;
550 }
551 }
552
553 // if one region is not contained in the new one, abort!
554 // then this is not well nested!
555 if (!((newRange->start->toCursor() <= (*it)->start->toCursor()) && (newRange->end->toCursor() >= (*it)->end->toCursor()))) {
556 return false;
557 }
558
559 // include into new nested ranges
560 nestedRanges.push_back((*it));
561
562 // end reached
563 if (it == upperBound) {
564 break;
565 }
566
567 // else increment
568 ++it;
569 }
570
571 // if we arrive here, all is nicely nested into our new range
572 // remove the contained ones here, insert new range with new nested ranges we already constructed
573 it = existingRanges.erase(lowerBound, includeUpperBound ? (upperBound + 1) : upperBound);
574 existingRanges.insert(it, newRange);
575 newRange->nestedRanges = nestedRanges;
576
577 // correct parent mapping!
578 newRange->parent = parent;
579 for (FoldingRange *range : std::as_const(newRange->nestedRanges)) {
580 range->parent = newRange;
581 }
582
583 // all nice
584 return true;
585}
586
587bool TextFolding::compareRangeByStart(FoldingRange *a, FoldingRange *b)
588{
589 return a->start->toCursor() < b->start->toCursor();
590}
591
592bool TextFolding::compareRangeByEnd(FoldingRange *a, FoldingRange *b)
593{
594 return a->end->toCursor() < b->end->toCursor();
595}
596
597bool TextFolding::compareRangeByStartWithLine(int line, FoldingRange *range)
598{
599 return (line < range->start->line());
600}
601
602bool TextFolding::compareRangeByLineWithStart(FoldingRange *range, int line)
603{
604 return (range->start->line() < line);
605}
606
607bool TextFolding::updateFoldedRangesForNewRange(TextFolding::FoldingRange *newRange)
608{
609 // not folded? not interesting! we don't need to touch our m_foldedFoldingRanges vector
610 if (!(newRange->flags & Folded)) {
611 return false;
612 }
613
614 // any of the parents folded? not interesting, too!
615 TextFolding::FoldingRange *parent = newRange->parent;
616 while (parent) {
617 // parent folded => be done
618 if (parent->flags & Folded) {
619 return false;
620 }
621
622 // walk up
624 }
625
626 // ok, if we arrive here, we are a folded range and we have no folded parent
627 // we now want to add this range to the m_foldedFoldingRanges vector, just removing any ranges that is included in it!
628 // TODO: OPTIMIZE
629 FoldingRange::Vector newFoldedFoldingRanges;
630 bool newRangeInserted = false;
631 for (FoldingRange *range : std::as_const(m_foldedFoldingRanges)) {
632 // contained? kill
633 if ((newRange->start->toCursor() <= range->start->toCursor()) && (newRange->end->toCursor() >= range->end->toCursor())) {
634 continue;
635 }
636
637 // range is behind newRange?
638 // insert newRange if not already done
639 if (!newRangeInserted && (range->start->toCursor() >= newRange->end->toCursor())) {
640 newFoldedFoldingRanges.push_back(newRange);
641 newRangeInserted = true;
642 }
643
644 // just transfer range
645 newFoldedFoldingRanges.push_back(range);
646 }
647
648 // last: insert new range, if not done
649 if (!newRangeInserted) {
650 newFoldedFoldingRanges.push_back(newRange);
651 }
652
653 // fixup folded ranges
654 m_foldedFoldingRanges = newFoldedFoldingRanges;
655
656 // folding changed!
658
659 // all fine, stuff done, signal emitted
660 return true;
661}
662
663bool TextFolding::updateFoldedRangesForRemovedRange(TextFolding::FoldingRange *oldRange)
664{
665 // folded? not interesting! we don't need to touch our m_foldedFoldingRanges vector
666 if (oldRange->flags & Folded) {
667 return false;
668 }
669
670 // any of the parents folded? not interesting, too!
671 TextFolding::FoldingRange *parent = oldRange->parent;
672 while (parent) {
673 // parent folded => be done
674 if (parent->flags & Folded) {
675 return false;
676 }
677
678 // walk up
680 }
681
682 // ok, if we arrive here, we are a unfolded range and we have no folded parent
683 // we now want to remove this range from the m_foldedFoldingRanges vector and include our nested folded ranges!
684 // TODO: OPTIMIZE
685 FoldingRange::Vector newFoldedFoldingRanges;
686 for (FoldingRange *range : std::as_const(m_foldedFoldingRanges)) {
687 // right range? insert folded nested ranges
688 if (range == oldRange) {
689 appendFoldedRanges(newFoldedFoldingRanges, oldRange->nestedRanges);
690 continue;
691 }
692
693 // just transfer range
694 newFoldedFoldingRanges.push_back(range);
695 }
696
697 // fixup folded ranges
698 m_foldedFoldingRanges = newFoldedFoldingRanges;
699
700 // folding changed!
702
703 // all fine, stuff done, signal emitted
704 return true;
705}
706
707void TextFolding::appendFoldedRanges(TextFolding::FoldingRange::Vector &newFoldedFoldingRanges, const TextFolding::FoldingRange::Vector &ranges) const
708{
709 // search for folded ranges and append them
710 for (FoldingRange *range : ranges) {
711 // itself folded? append
712 if (range->flags & Folded) {
713 newFoldedFoldingRanges.push_back(range);
714 continue;
715 }
716
717 // else: recurse!
718 appendFoldedRanges(newFoldedFoldingRanges, range->nestedRanges);
719 }
720}
721
723{
724 QJsonObject obj;
725 QJsonArray array;
726 exportFoldingRanges(m_foldingRanges, array);
727 obj.insert(QStringLiteral("ranges"), array);
728 obj.insert(QStringLiteral("checksum"), QString::fromLocal8Bit(m_buffer.digest().toHex()));
729 QJsonDocument folds;
730 folds.setObject(obj);
731 return folds;
732}
733
735{
736 // dump all ranges recursively
737 for (FoldingRange *range : ranges) {
738 // construct one range and dump to folds
739 QJsonObject rangeMap;
740 rangeMap[QStringLiteral("startLine")] = range->start->line();
741 rangeMap[QStringLiteral("startColumn")] = range->start->column();
742 rangeMap[QStringLiteral("endLine")] = range->end->line();
743 rangeMap[QStringLiteral("endColumn")] = range->end->column();
744 rangeMap[QStringLiteral("flags")] = (int)range->flags;
745 folds.append(rangeMap);
746
747 // recurse
748 exportFoldingRanges(range->nestedRanges, folds);
749 }
750}
751
753{
754 clearFoldingRanges();
755
756 const auto checksum = QByteArray::fromHex(folds.object().value(QStringLiteral("checksum")).toString().toLocal8Bit());
757 if (checksum != m_buffer.digest()) {
758 return;
759 }
760
761 // try to create all folding ranges
762 const auto jsonRanges = folds.object().value(QStringLiteral("ranges")).toArray();
763 for (const auto &rangeVariant : jsonRanges) {
764 // get map
765 const auto rangeMap = rangeVariant.toObject();
766
767 // construct range start/end
768 const KTextEditor::Cursor start(rangeMap[QStringLiteral("startLine")].toInt(), rangeMap[QStringLiteral("startColumn")].toInt());
769 const KTextEditor::Cursor end(rangeMap[QStringLiteral("endLine")].toInt(), rangeMap[QStringLiteral("endColumn")].toInt());
770
771 // check validity (required when loading a possibly broken folding state from disk)
772 auto doc = m_buffer.document();
773 if (start >= end || !doc->isValidTextPosition(start) || !doc->isValidTextPosition(end)) {
774 continue;
775 }
776
777 // get flags
778 const int rawFlags = rangeMap[QStringLiteral("flags")].toInt();
779 FoldingRangeFlags flags;
780 if (rawFlags & Persistent) {
781 flags = Persistent;
782 }
783 if (rawFlags & Folded) {
784 flags = Folded;
785 }
786
787 // create folding range
789 }
790}
791
792}
793
794#include "moc_katetextfolding.cpp"
The Cursor represents a position in a Document.
Definition cursor.h:75
const Cursor toCursor() const
Convert this clever cursor into a dumb one.
bool isValid() const
Returns whether the current position of this cursor is a valid position, i.e.
An object representing a section of text, from one Cursor to another.
constexpr bool isEmpty() const noexcept
Returns true if this range contains no characters, ie.
static constexpr Range invalid() noexcept
Returns an invalid range.
constexpr bool isValid() const noexcept
Validity check.
Class representing a text buffer.
void cleared()
Buffer got cleared.
int lines() const
Lines currently stored in this buffer.
KTextEditor::DocumentPrivate * document() const
Gets the document to which this buffer is bound.
const QByteArray & digest() const
Checksum of the document on disk, set either through file loading in openFile() or in KTextEditor::Do...
QList< QPair< qint64, FoldingRangeFlags > > foldingRangesForParentRange(qint64 parentRangeId=-1) const
Query child folding ranges for given range id.
bool unfoldRange(qint64 id, bool remove=false)
Unfold the given range.
void clear()
Clear the complete folding.
QJsonDocument exportFoldingRanges() const
Return the current known folding ranges a QJsonDocument to store in configs.
bool isLineVisible(int line, qint64 *foldedRangeId=nullptr) const
Query if a given line is visible.
QString debugDump() const
Dump folding state as string, for unit testing and debugging.
void foldingRangesChanged()
If the folding state of existing ranges changes or ranges are added/removed, this signal is emitted.
void importFoldingRanges(const QJsonDocument &folds)
Import the folding ranges given as a QJsonDocument like read from configs.
void debugPrint(const QString &title) const
Print state to stdout for testing.
QList< QPair< qint64, FoldingRangeFlags > > foldingRangesStartingOnLine(int line) const
Queries which folding ranges start at the given line and returns the id + flags for all of them.
qint64 newFoldingRange(KTextEditor::Range range, FoldingRangeFlags flags=FoldingRangeFlags())
Create a new folding range.
int visibleLines() const
Query number of visible lines.
int lineToVisibleLine(int line) const
Convert a text buffer line to a visible line number.
bool foldRange(qint64 id)
Fold the given range.
@ Folded
Range is folded away.
@ Persistent
Range is persistent, e.g.
KTextEditor::Range foldingRange(qint64 id) const
Returns the folding range associated with id.
~TextFolding() override
Cleanup.
int visibleLineToLine(int visibleLine) const
Convert a visible line number to a line number in the text buffer.
void ensureLineIsVisible(int line)
Ensure that a given line will be visible.
Q_SCRIPTABLE Q_NOREPLY void start()
const QList< QKeySequence > & end()
The KTextEditor namespace contains all the public API that is required to use the KTextEditor compone...
QByteArray fromHex(const QByteArray &hexEncoded)
QByteArray toHex(char separator) const const
void clear()
iterator insert(const Key &key, const T &value)
bool isEmpty() const const
bool remove(const Key &key)
T value(const Key &key) const const
void append(const QJsonValue &value)
QJsonObject object() const const
void setObject(const QJsonObject &object)
iterator end()
iterator insert(QLatin1StringView key, const QJsonValue &value)
QJsonValue value(QLatin1StringView key) const const
QJsonArray toArray() const const
void append(QList< T > &&value)
iterator begin()
void clear()
iterator end()
iterator erase(const_iterator begin, const_iterator end)
bool isEmpty() const const
void push_back(parameter_type value)
bool removeOne(const AT &t)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
QObject * parent() const const
QString arg(Args &&... args) const const
QString fromLocal8Bit(QByteArrayView str)
bool isEmpty() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Sat Dec 21 2024 17:01:55 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.