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(c: nestedRanges);
32}
33
34TextFolding::TextFolding(TextBuffer &buffer)
35 : QObject()
36 , m_buffer(buffer)
37 , m_idCounter(-1)
38{
39 // connect needed signals from buffer
40 connect(sender: &m_buffer, signal: &TextBuffer::cleared, context: this, slot: &TextFolding::clear);
41}
42
43TextFolding::~TextFolding()
44{
45 // only delete the folding ranges, the folded ranges and mapped ranges are the same objects
46 qDeleteAll(c: m_foldingRanges);
47}
48
49void TextFolding::clear()
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(c: m_foldingRanges);
70 m_foldingRanges.clear();
71
72 // folding changed!
73 Q_EMIT foldingRangesChanged();
74}
75
76qint64 TextFolding::newFoldingRange(KTextEditor::Range range, FoldingRangeFlags flags)
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(parent: nullptr /* no parent here */, existingRanges&: 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(key: newRange->id, value: 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) {
112 Q_EMIT foldingRangesChanged();
113 }
114
115 // all went fine, newRange is now registered internally!
116 return newRange->id;
117}
118
119KTextEditor::Range TextFolding::foldingRange(qint64 id) const
120{
121 FoldingRange *range = m_idToFoldingRange.value(key: id);
122 if (!range) {
123 return KTextEditor::Range::invalid();
124 }
125
126 return KTextEditor::Range(range->start->toCursor(), range->end->toCursor());
127}
128
129bool TextFolding::foldRange(qint64 id)
130{
131 // try to find the range, else bail out
132 FoldingRange *range = m_idToFoldingRange.value(key: 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(newRange: 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(key: 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(key: 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(t&: parentVector)) {
175 // insert our nested ranges and reparent them
176 if (curRange == range) {
177 for (FoldingRange *newRange : std::as_const(t&: range->nestedRanges)) {
178 newRange->parent = range->parent;
179 newParentVector.push_back(t: newRange);
180 }
181
182 continue;
183 }
184
185 // else just transfer elements
186 newParentVector.push_back(t: 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(oldRange: 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) {
201 Q_EMIT foldingRangesChanged();
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(first: m_foldedFoldingRanges.begin(), last: m_foldedFoldingRanges.end(), val: line, comp: 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
241void TextFolding::ensureLineIsVisible(int line)
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: &foldedRangeId)) {
251 // id should be valid!
252 Q_ASSERT(foldedRangeId >= 0);
253
254 // unfold shall work!
255 const bool unfolded = unfoldRange(id: foldedRangeId);
256 (void)unfolded;
257 Q_ASSERT(unfolded);
258 }
259}
260
261int TextFolding::visibleLines() const
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
281int TextFolding::lineToVisibleLine(int line) const
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
363QList<QPair<qint64, TextFolding::FoldingRangeFlags>> TextFolding::foldingRangesStartingOnLine(int line) const
364{
365 // results vector
366 QList<QPair<qint64, TextFolding::FoldingRangeFlags>> results;
367
368 // recursively do binary search
369 foldingRangesStartingOnLine(results, ranges: 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(first: ranges.begin(), last: ranges.end(), val: line, comp: compareRangeByLineWithStart);
384
385 // second: upper bound of end
386 FoldingRange::Vector::const_iterator upperBound = std::upper_bound(first: ranges.begin(), last: ranges.end(), val: line, comp: 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(t: qMakePair(value1&: (*it)->id, value2&: (*it)->flags));
398 }
399
400 // recurse anyway
401 foldingRangesStartingOnLine(results, ranges: (*it)->nestedRanges, line);
402 }
403}
404
405QList<QPair<qint64, TextFolding::FoldingRangeFlags>> TextFolding::foldingRangesForParentRange(qint64 parentRangeId) const
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(key: parentRangeId)) {
412 ranges = &range->nestedRanges;
413 }
414
415 // no ranges => nothing to do
416 QList<QPair<qint64, FoldingRangeFlags>> results;
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(t: qMakePair(value1&: (*it)->id, value2&: (*it)->flags));
424 }
425 return results;
426}
427
428QString TextFolding::debugDump() const
429{
430 // dump toplevel ranges recursively
431 return QStringLiteral("tree %1 - folded %2").arg(args: debugDump(ranges: m_foldingRanges, recurse: true), args: debugDump(ranges: m_foldedFoldingRanges, recurse: false));
432}
433
434void TextFolding::debugPrint(const QString &title) const
435{
436 // print title + content
437 printf(format: "%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(first: m_foldedFoldingRanges.begin(), last: m_foldedFoldingRanges.end(), val: startLine, comp: 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(t: *foldIt);
459 m_idToFoldingRange.remove(key: (*foldIt)->id);
460 delete *foldIt;
461 foldIt = m_foldedFoldingRanges.erase(pos: 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) {
468 Q_EMIT foldingRangesChanged();
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(a: range->start->line()).arg(a: range->start->column()).arg(args: persistent, args: folded);
484
485 // recurse
486 if (recurse) {
487 QString inner = debugDump(ranges: range->nestedRanges, recurse);
488 if (!inner.isEmpty()) {
489 dump += inner + QLatin1Char(' ');
490 }
491 }
492
493 dump += QStringLiteral("%1:%2]").arg(a: range->end->line()).arg(a: 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(first: existingRanges.begin(), last: existingRanges.end(), val: newRange, comp: compareRangeByStart);
505
506 // second: upper bound of end
507 FoldingRange::Vector::iterator upperBound = std::upper_bound(first: existingRanges.begin(), last: existingRanges.end(), val: newRange, comp: 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(before: lowerBound, t: 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(parent: (*lowerBound), existingRanges&: (*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(t: (*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(abegin: lowerBound, aend: includeUpperBound ? (upperBound + 1) : upperBound);
574 existingRanges.insert(before: it, t: newRange);
575 newRange->nestedRanges = nestedRanges;
576
577 // correct parent mapping!
578 newRange->parent = parent;
579 for (FoldingRange *range : std::as_const(t&: 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
623 parent = parent->parent;
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(t&: 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(t: newRange);
641 newRangeInserted = true;
642 }
643
644 // just transfer range
645 newFoldedFoldingRanges.push_back(t: range);
646 }
647
648 // last: insert new range, if not done
649 if (!newRangeInserted) {
650 newFoldedFoldingRanges.push_back(t: newRange);
651 }
652
653 // fixup folded ranges
654 m_foldedFoldingRanges = newFoldedFoldingRanges;
655
656 // folding changed!
657 Q_EMIT foldingRangesChanged();
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
679 parent = parent->parent;
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(t&: m_foldedFoldingRanges)) {
687 // right range? insert folded nested ranges
688 if (range == oldRange) {
689 appendFoldedRanges(newFoldedFoldingRanges, ranges: oldRange->nestedRanges);
690 continue;
691 }
692
693 // just transfer range
694 newFoldedFoldingRanges.push_back(t: range);
695 }
696
697 // fixup folded ranges
698 m_foldedFoldingRanges = newFoldedFoldingRanges;
699
700 // folding changed!
701 Q_EMIT foldingRangesChanged();
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(t: range);
714 continue;
715 }
716
717 // else: recurse!
718 appendFoldedRanges(newFoldedFoldingRanges, ranges: range->nestedRanges);
719 }
720}
721
722QJsonDocument TextFolding::exportFoldingRanges() const
723{
724 QJsonObject obj;
725 QJsonArray array;
726 exportFoldingRanges(ranges: m_foldingRanges, folds&: array);
727 obj.insert(QStringLiteral("ranges"), value: array);
728 obj.insert(QStringLiteral("checksum"), value: QString::fromLocal8Bit(ba: m_buffer.digest().toHex()));
729 QJsonDocument folds;
730 folds.setObject(obj);
731 return folds;
732}
733
734void TextFolding::exportFoldingRanges(const TextFolding::FoldingRange::Vector &ranges, QJsonArray &folds)
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(value: rangeMap);
746
747 // recurse
748 exportFoldingRanges(ranges: range->nestedRanges, folds);
749 }
750}
751
752void TextFolding::importFoldingRanges(const QJsonDocument &folds)
753{
754 clearFoldingRanges();
755
756 const auto checksum = QByteArray::fromHex(hexEncoded: 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(cursor: start) || !doc->isValidTextPosition(cursor: 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
788 newFoldingRange(range: KTextEditor::Range(start, end), flags);
789 }
790}
791
792}
793
794#include "moc_katetextfolding.cpp"
795

source code of ktexteditor/src/buffer/katetextfolding.cpp