File indexing completed on 2024-04-21 03:57:18

0001 /*
0002     SPDX-FileCopyrightText: 2013 Christoph Cullmann <cullmann@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "katetextfolding.h"
0008 #include "katedocument.h"
0009 #include "katetextbuffer.h"
0010 #include "katetextrange.h"
0011 
0012 #include <QJsonObject>
0013 
0014 namespace Kate
0015 {
0016 TextFolding::FoldingRange::FoldingRange(TextBuffer &buffer, KTextEditor::Range range, FoldingRangeFlags _flags)
0017     : start(new TextCursor(buffer, range.start(), KTextEditor::MovingCursor::MoveOnInsert))
0018     , end(new TextCursor(buffer, range.end(), KTextEditor::MovingCursor::MoveOnInsert))
0019     , parent(nullptr)
0020     , flags(_flags)
0021     , id(-1)
0022 {
0023 }
0024 
0025 TextFolding::FoldingRange::~FoldingRange()
0026 {
0027     // kill all our data!
0028     // this will recurse all sub-structures!
0029     delete start;
0030     delete end;
0031     qDeleteAll(nestedRanges);
0032 }
0033 
0034 TextFolding::TextFolding(TextBuffer &buffer)
0035     : QObject()
0036     , m_buffer(buffer)
0037     , m_idCounter(-1)
0038 {
0039     // connect needed signals from buffer
0040     connect(&m_buffer, &TextBuffer::cleared, this, &TextFolding::clear);
0041 }
0042 
0043 TextFolding::~TextFolding()
0044 {
0045     // only delete the folding ranges, the folded ranges and mapped ranges are the same objects
0046     qDeleteAll(m_foldingRanges);
0047 }
0048 
0049 void TextFolding::clear()
0050 {
0051     // reset counter
0052     m_idCounter = -1;
0053     clearFoldingRanges();
0054 }
0055 
0056 void TextFolding::clearFoldingRanges()
0057 {
0058     // no ranges, no work
0059     if (m_foldingRanges.isEmpty()) {
0060         // assert all stuff is consistent and return!
0061         Q_ASSERT(m_idToFoldingRange.isEmpty());
0062         Q_ASSERT(m_foldedFoldingRanges.isEmpty());
0063         return;
0064     }
0065 
0066     // cleanup
0067     m_idToFoldingRange.clear();
0068     m_foldedFoldingRanges.clear();
0069     qDeleteAll(m_foldingRanges);
0070     m_foldingRanges.clear();
0071 
0072     // folding changed!
0073     Q_EMIT foldingRangesChanged();
0074 }
0075 
0076 qint64 TextFolding::newFoldingRange(KTextEditor::Range range, FoldingRangeFlags flags)
0077 {
0078     // sort out invalid and empty ranges
0079     // that makes no sense, they will never grow again!
0080     if (!range.isValid() || range.isEmpty()) {
0081         return -1;
0082     }
0083 
0084     // create new folding region that we want to insert
0085     // this will internally create moving cursors!
0086     FoldingRange *newRange = new FoldingRange(m_buffer, range, flags);
0087 
0088     // the construction of the text cursors might have invalidated this
0089     // check and bail out if that happens
0090     // bail out, too, if it can't be inserted!
0091     if (!newRange->start->isValid() || !newRange->end->isValid() || !insertNewFoldingRange(nullptr /* no parent here */, m_foldingRanges, newRange)) {
0092         // cleanup and be done
0093         delete newRange;
0094         return -1;
0095     }
0096 
0097     // set id, catch overflows, even if they shall not happen
0098     newRange->id = ++m_idCounter;
0099     if (newRange->id < 0) {
0100         newRange->id = m_idCounter = 0;
0101     }
0102 
0103     // remember the range
0104     m_idToFoldingRange.insert(newRange->id, newRange);
0105 
0106     // update our folded ranges vector!
0107     bool updated = updateFoldedRangesForNewRange(newRange);
0108 
0109     // emit that something may have changed
0110     // do that only, if updateFoldedRangesForNewRange did not already do the job!
0111     if (!updated) {
0112         Q_EMIT foldingRangesChanged();
0113     }
0114 
0115     // all went fine, newRange is now registered internally!
0116     return newRange->id;
0117 }
0118 
0119 KTextEditor::Range TextFolding::foldingRange(qint64 id) const
0120 {
0121     FoldingRange *range = m_idToFoldingRange.value(id);
0122     if (!range) {
0123         return KTextEditor::Range::invalid();
0124     }
0125 
0126     return KTextEditor::Range(range->start->toCursor(), range->end->toCursor());
0127 }
0128 
0129 bool TextFolding::foldRange(qint64 id)
0130 {
0131     // try to find the range, else bail out
0132     FoldingRange *range = m_idToFoldingRange.value(id);
0133     if (!range) {
0134         return false;
0135     }
0136 
0137     // already folded? nothing to do
0138     if (range->flags & Folded) {
0139         return true;
0140     }
0141 
0142     // fold and be done
0143     range->flags |= Folded;
0144     updateFoldedRangesForNewRange(range);
0145     return true;
0146 }
0147 
0148 bool TextFolding::unfoldRange(qint64 id, bool remove)
0149 {
0150     // try to find the range, else bail out
0151     FoldingRange *range = m_idToFoldingRange.value(id);
0152     if (!range) {
0153         return false;
0154     }
0155 
0156     // nothing to do?
0157     // range is already unfolded and we need not to remove it!
0158     if (!remove && !(range->flags & Folded)) {
0159         return true;
0160     }
0161 
0162     // do we need to delete the range?
0163     const bool deleteRange = remove || !(range->flags & Persistent);
0164 
0165     // first: remove the range, if forced or non-persistent!
0166     if (deleteRange) {
0167         // remove from outside visible mapping!
0168         m_idToFoldingRange.remove(id);
0169 
0170         // remove from folding vectors!
0171         // FIXME: OPTIMIZE
0172         FoldingRange::Vector &parentVector = range->parent ? range->parent->nestedRanges : m_foldingRanges;
0173         FoldingRange::Vector newParentVector;
0174         for (FoldingRange *curRange : std::as_const(parentVector)) {
0175             // insert our nested ranges and reparent them
0176             if (curRange == range) {
0177                 for (FoldingRange *newRange : std::as_const(range->nestedRanges)) {
0178                     newRange->parent = range->parent;
0179                     newParentVector.push_back(newRange);
0180                 }
0181 
0182                 continue;
0183             }
0184 
0185             // else just transfer elements
0186             newParentVector.push_back(curRange);
0187         }
0188         parentVector = newParentVector;
0189     }
0190 
0191     // second: unfold the range, if needed!
0192     bool updated = false;
0193     if (range->flags & Folded) {
0194         range->flags &= ~Folded;
0195         updated = updateFoldedRangesForRemovedRange(range);
0196     }
0197 
0198     // emit that something may have changed
0199     // do that only, if updateFoldedRangesForRemoveRange did not already do the job!
0200     if (!updated) {
0201         Q_EMIT foldingRangesChanged();
0202     }
0203 
0204     // really delete the range, if needed!
0205     if (deleteRange) {
0206         // clear ranges first, they got moved!
0207         range->nestedRanges.clear();
0208         delete range;
0209     }
0210 
0211     // be done ;)
0212     return true;
0213 }
0214 
0215 bool TextFolding::isLineVisible(int line, qint64 *foldedRangeId) const
0216 {
0217     // skip if nothing folded
0218     if (m_foldedFoldingRanges.isEmpty()) {
0219         return true;
0220     }
0221 
0222     // search upper bound, index to item with start line higher than our one
0223     FoldingRange::Vector::const_iterator upperBound =
0224         std::upper_bound(m_foldedFoldingRanges.begin(), m_foldedFoldingRanges.end(), line, compareRangeByStartWithLine);
0225     if (upperBound != m_foldedFoldingRanges.begin()) {
0226         --upperBound;
0227     }
0228 
0229     // check if we overlap with the range in front of us
0230     const bool hidden = (((*upperBound)->end->line() >= line) && (line > (*upperBound)->start->line()));
0231 
0232     // fill in folded range id, if needed
0233     if (foldedRangeId) {
0234         (*foldedRangeId) = hidden ? (*upperBound)->id : -1;
0235     }
0236 
0237     // visible == !hidden
0238     return !hidden;
0239 }
0240 
0241 void TextFolding::ensureLineIsVisible(int line)
0242 {
0243     // skip if nothing folded
0244     if (m_foldedFoldingRanges.isEmpty()) {
0245         return;
0246     }
0247 
0248     // while not visible, unfold
0249     qint64 foldedRangeId = -1;
0250     while (!isLineVisible(line, &foldedRangeId)) {
0251         // id should be valid!
0252         Q_ASSERT(foldedRangeId >= 0);
0253 
0254         // unfold shall work!
0255         const bool unfolded = unfoldRange(foldedRangeId);
0256         (void)unfolded;
0257         Q_ASSERT(unfolded);
0258     }
0259 }
0260 
0261 int TextFolding::visibleLines() const
0262 {
0263     // start with all lines we have
0264     int visibleLines = m_buffer.lines();
0265 
0266     // skip if nothing folded
0267     if (m_foldedFoldingRanges.isEmpty()) {
0268         return visibleLines;
0269     }
0270 
0271     // count all folded lines and subtract them from visible lines
0272     for (FoldingRange *range : m_foldedFoldingRanges) {
0273         visibleLines -= (range->end->line() - range->start->line());
0274     }
0275 
0276     // be done, assert we did no trash
0277     Q_ASSERT(visibleLines > 0);
0278     return visibleLines;
0279 }
0280 
0281 int TextFolding::lineToVisibleLine(int line) const
0282 {
0283     // valid input needed!
0284     Q_ASSERT(line >= 0);
0285 
0286     // start with identity
0287     int visibleLine = line;
0288 
0289     // skip if nothing folded or first line
0290     if (m_foldedFoldingRanges.isEmpty() || (line == 0)) {
0291         return visibleLine;
0292     }
0293 
0294     // walk over all folded ranges until we reach the line
0295     // keep track of seen visible lines, for the case we want to convert a hidden line!
0296     int seenVisibleLines = 0;
0297     int lastLine = 0;
0298     for (FoldingRange *range : m_foldedFoldingRanges) {
0299         // abort if we reach our line!
0300         if (range->start->line() >= line) {
0301             break;
0302         }
0303 
0304         // count visible lines
0305         seenVisibleLines += (range->start->line() - lastLine);
0306         lastLine = range->end->line();
0307 
0308         // we might be contained in the region, then we return last visible line
0309         if (line <= range->end->line()) {
0310             return seenVisibleLines;
0311         }
0312 
0313         // subtrace folded lines
0314         visibleLine -= (range->end->line() - range->start->line());
0315     }
0316 
0317     // be done, assert we did no trash
0318     Q_ASSERT(visibleLine >= 0);
0319     return visibleLine;
0320 }
0321 
0322 int TextFolding::visibleLineToLine(int visibleLine) const
0323 {
0324     // valid input needed!
0325     Q_ASSERT(visibleLine >= 0);
0326 
0327     // start with identity
0328     int line = visibleLine;
0329 
0330     // skip if nothing folded or first line
0331     if (m_foldedFoldingRanges.isEmpty() || (visibleLine == 0)) {
0332         return line;
0333     }
0334 
0335     // last visible line seen, as line in buffer
0336     int seenVisibleLines = 0;
0337     int lastLine = 0;
0338     int lastLineVisibleLines = 0;
0339     for (FoldingRange *range : m_foldedFoldingRanges) {
0340         // else compute visible lines and move last seen
0341         lastLineVisibleLines = seenVisibleLines;
0342         seenVisibleLines += (range->start->line() - lastLine);
0343 
0344         // bail out if enough seen
0345         if (seenVisibleLines >= visibleLine) {
0346             break;
0347         }
0348 
0349         lastLine = range->end->line();
0350     }
0351 
0352     // check if still no enough visible!
0353     if (seenVisibleLines < visibleLine) {
0354         lastLineVisibleLines = seenVisibleLines;
0355     }
0356 
0357     // compute visible line
0358     line = (lastLine + (visibleLine - lastLineVisibleLines));
0359     Q_ASSERT(line >= 0);
0360     return line;
0361 }
0362 
0363 QList<QPair<qint64, TextFolding::FoldingRangeFlags>> TextFolding::foldingRangesStartingOnLine(int line) const
0364 {
0365     // results vector
0366     QList<QPair<qint64, TextFolding::FoldingRangeFlags>> results;
0367 
0368     // recursively do binary search
0369     foldingRangesStartingOnLine(results, m_foldingRanges, line);
0370 
0371     // return found results
0372     return results;
0373 }
0374 
0375 void TextFolding::foldingRangesStartingOnLine(QList<QPair<qint64, FoldingRangeFlags>> &results, const TextFolding::FoldingRange::Vector &ranges, int line) const
0376 {
0377     // early out for no folds
0378     if (ranges.isEmpty()) {
0379         return;
0380     }
0381 
0382     // first: lower bound of start
0383     FoldingRange::Vector::const_iterator lowerBound = std::lower_bound(ranges.begin(), ranges.end(), line, compareRangeByLineWithStart);
0384 
0385     // second: upper bound of end
0386     FoldingRange::Vector::const_iterator upperBound = std::upper_bound(ranges.begin(), ranges.end(), line, compareRangeByStartWithLine);
0387 
0388     // 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!
0389     if ((lowerBound != ranges.begin()) && ((*(lowerBound - 1))->end->line() >= line)) {
0390         --lowerBound;
0391     }
0392 
0393     // for all of them, check if we start at right line and recurse
0394     for (FoldingRange::Vector::const_iterator it = lowerBound; it != upperBound; ++it) {
0395         // this range already ok? add it to results
0396         if ((*it)->start->line() == line) {
0397             results.push_back(qMakePair((*it)->id, (*it)->flags));
0398         }
0399 
0400         // recurse anyway
0401         foldingRangesStartingOnLine(results, (*it)->nestedRanges, line);
0402     }
0403 }
0404 
0405 QList<QPair<qint64, TextFolding::FoldingRangeFlags>> TextFolding::foldingRangesForParentRange(qint64 parentRangeId) const
0406 {
0407     // toplevel ranges requested or real parent?
0408     const FoldingRange::Vector *ranges = nullptr;
0409     if (parentRangeId == -1) {
0410         ranges = &m_foldingRanges;
0411     } else if (FoldingRange *range = m_idToFoldingRange.value(parentRangeId)) {
0412         ranges = &range->nestedRanges;
0413     }
0414 
0415     // no ranges => nothing to do
0416     QList<QPair<qint64, FoldingRangeFlags>> results;
0417     if (!ranges) {
0418         return results;
0419     }
0420 
0421     // else convert ranges to id + flags and pass that back
0422     for (FoldingRange::Vector::const_iterator it = ranges->begin(); it != ranges->end(); ++it) {
0423         results.append(qMakePair((*it)->id, (*it)->flags));
0424     }
0425     return results;
0426 }
0427 
0428 QString TextFolding::debugDump() const
0429 {
0430     // dump toplevel ranges recursively
0431     return QStringLiteral("tree %1 - folded %2").arg(debugDump(m_foldingRanges, true), debugDump(m_foldedFoldingRanges, false));
0432 }
0433 
0434 void TextFolding::debugPrint(const QString &title) const
0435 {
0436     // print title + content
0437     printf("%s\n    %s\n", qPrintable(title), qPrintable(debugDump()));
0438 }
0439 
0440 void TextFolding::editEnd(int startLine, int endLine, std::function<bool(int)> isLineFoldingStart)
0441 {
0442     // search upper bound, index to item with start line higher than our one
0443     auto foldIt = std::upper_bound(m_foldedFoldingRanges.begin(), m_foldedFoldingRanges.end(), startLine, compareRangeByStartWithLine);
0444     if (foldIt != m_foldedFoldingRanges.begin()) {
0445         --foldIt;
0446     }
0447 
0448     // handle all ranges until we go behind the last line
0449     bool anyUpdate = false;
0450     while (foldIt != m_foldedFoldingRanges.end() && (*foldIt)->start->line() <= endLine) {
0451         // shall we keep this folding?
0452         if (isLineFoldingStart((*foldIt)->start->line())) {
0453             ++foldIt;
0454             continue;
0455         }
0456 
0457         // else kill it
0458         m_foldingRanges.removeOne(*foldIt);
0459         m_idToFoldingRange.remove((*foldIt)->id);
0460         delete *foldIt;
0461         foldIt = m_foldedFoldingRanges.erase(foldIt);
0462         anyUpdate = true;
0463     }
0464 
0465     // ensure we do the proper updates outside
0466     // we might change some range outside of the lines we edited
0467     if (anyUpdate) {
0468         Q_EMIT foldingRangesChanged();
0469     }
0470 }
0471 
0472 QString TextFolding::debugDump(const TextFolding::FoldingRange::Vector &ranges, bool recurse)
0473 {
0474     // dump all ranges recursively
0475     QString dump;
0476     for (FoldingRange *range : ranges) {
0477         if (!dump.isEmpty()) {
0478             dump += QLatin1Char(' ');
0479         }
0480 
0481         const QString persistent = (range->flags & Persistent) ? QStringLiteral("p") : QString();
0482         const QString folded = (range->flags & Folded) ? QStringLiteral("f") : QString();
0483         dump += QStringLiteral("[%1:%2 %3%4 ").arg(range->start->line()).arg(range->start->column()).arg(persistent, folded);
0484 
0485         // recurse
0486         if (recurse) {
0487             QString inner = debugDump(range->nestedRanges, recurse);
0488             if (!inner.isEmpty()) {
0489                 dump += inner + QLatin1Char(' ');
0490             }
0491         }
0492 
0493         dump += QStringLiteral("%1:%2]").arg(range->end->line()).arg(range->end->column());
0494     }
0495     return dump;
0496 }
0497 
0498 bool TextFolding::insertNewFoldingRange(FoldingRange *parent, FoldingRange::Vector &existingRanges, FoldingRange *newRange)
0499 {
0500     // existing ranges are non-overlapping and sorted
0501     // that means, we can search for lower bound of start of range and upper bound of end of range to find all "overlapping" ranges.
0502 
0503     // first: lower bound of start
0504     FoldingRange::Vector::iterator lowerBound = std::lower_bound(existingRanges.begin(), existingRanges.end(), newRange, compareRangeByStart);
0505 
0506     // second: upper bound of end
0507     FoldingRange::Vector::iterator upperBound = std::upper_bound(existingRanges.begin(), existingRanges.end(), newRange, compareRangeByEnd);
0508 
0509     // 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!
0510     if ((lowerBound != existingRanges.begin()) && ((*(lowerBound - 1))->end->toCursor() > newRange->start->toCursor())) {
0511         --lowerBound;
0512     }
0513 
0514     // now: first case, we overlap with nothing or hit exactly one range!
0515     if (lowerBound == upperBound) {
0516         // nothing we overlap with?
0517         // then just insert and be done!
0518         if ((lowerBound == existingRanges.end()) || (newRange->start->toCursor() >= (*lowerBound)->end->toCursor())
0519             || (newRange->end->toCursor() <= (*lowerBound)->start->toCursor())) {
0520             // insert + fix parent
0521             existingRanges.insert(lowerBound, newRange);
0522             newRange->parent = parent;
0523 
0524             // all done
0525             return true;
0526         }
0527 
0528         // we are contained in this one range?
0529         // then recurse!
0530         if ((newRange->start->toCursor() >= (*lowerBound)->start->toCursor()) && (newRange->end->toCursor() <= (*lowerBound)->end->toCursor())) {
0531             return insertNewFoldingRange((*lowerBound), (*lowerBound)->nestedRanges, newRange);
0532         }
0533 
0534         // else: we might contain at least this fold, or many more, if this if block is not taken at all
0535         // use the general code that checks for "we contain stuff" below!
0536     }
0537 
0538     // check if we contain other folds!
0539     FoldingRange::Vector::iterator it = lowerBound;
0540     bool includeUpperBound = false;
0541     FoldingRange::Vector nestedRanges;
0542     while (it != existingRanges.end()) {
0543         // do we need to take look at upper bound, too?
0544         // if not break
0545         if (it == upperBound) {
0546             if (newRange->end->toCursor() <= (*upperBound)->start->toCursor()) {
0547                 break;
0548             } else {
0549                 includeUpperBound = true;
0550             }
0551         }
0552 
0553         // if one region is not contained in the new one, abort!
0554         // then this is not well nested!
0555         if (!((newRange->start->toCursor() <= (*it)->start->toCursor()) && (newRange->end->toCursor() >= (*it)->end->toCursor()))) {
0556             return false;
0557         }
0558 
0559         // include into new nested ranges
0560         nestedRanges.push_back((*it));
0561 
0562         // end reached
0563         if (it == upperBound) {
0564             break;
0565         }
0566 
0567         // else increment
0568         ++it;
0569     }
0570 
0571     // if we arrive here, all is nicely nested into our new range
0572     // remove the contained ones here, insert new range with new nested ranges we already constructed
0573     it = existingRanges.erase(lowerBound, includeUpperBound ? (upperBound + 1) : upperBound);
0574     existingRanges.insert(it, newRange);
0575     newRange->nestedRanges = nestedRanges;
0576 
0577     // correct parent mapping!
0578     newRange->parent = parent;
0579     for (FoldingRange *range : std::as_const(newRange->nestedRanges)) {
0580         range->parent = newRange;
0581     }
0582 
0583     // all nice
0584     return true;
0585 }
0586 
0587 bool TextFolding::compareRangeByStart(FoldingRange *a, FoldingRange *b)
0588 {
0589     return a->start->toCursor() < b->start->toCursor();
0590 }
0591 
0592 bool TextFolding::compareRangeByEnd(FoldingRange *a, FoldingRange *b)
0593 {
0594     return a->end->toCursor() < b->end->toCursor();
0595 }
0596 
0597 bool TextFolding::compareRangeByStartWithLine(int line, FoldingRange *range)
0598 {
0599     return (line < range->start->line());
0600 }
0601 
0602 bool TextFolding::compareRangeByLineWithStart(FoldingRange *range, int line)
0603 {
0604     return (range->start->line() < line);
0605 }
0606 
0607 bool TextFolding::updateFoldedRangesForNewRange(TextFolding::FoldingRange *newRange)
0608 {
0609     // not folded? not interesting! we don't need to touch our m_foldedFoldingRanges vector
0610     if (!(newRange->flags & Folded)) {
0611         return false;
0612     }
0613 
0614     // any of the parents folded? not interesting, too!
0615     TextFolding::FoldingRange *parent = newRange->parent;
0616     while (parent) {
0617         // parent folded => be done
0618         if (parent->flags & Folded) {
0619             return false;
0620         }
0621 
0622         // walk up
0623         parent = parent->parent;
0624     }
0625 
0626     // ok, if we arrive here, we are a folded range and we have no folded parent
0627     // we now want to add this range to the m_foldedFoldingRanges vector, just removing any ranges that is included in it!
0628     // TODO: OPTIMIZE
0629     FoldingRange::Vector newFoldedFoldingRanges;
0630     bool newRangeInserted = false;
0631     for (FoldingRange *range : std::as_const(m_foldedFoldingRanges)) {
0632         // contained? kill
0633         if ((newRange->start->toCursor() <= range->start->toCursor()) && (newRange->end->toCursor() >= range->end->toCursor())) {
0634             continue;
0635         }
0636 
0637         // range is behind newRange?
0638         // insert newRange if not already done
0639         if (!newRangeInserted && (range->start->toCursor() >= newRange->end->toCursor())) {
0640             newFoldedFoldingRanges.push_back(newRange);
0641             newRangeInserted = true;
0642         }
0643 
0644         // just transfer range
0645         newFoldedFoldingRanges.push_back(range);
0646     }
0647 
0648     // last: insert new range, if not done
0649     if (!newRangeInserted) {
0650         newFoldedFoldingRanges.push_back(newRange);
0651     }
0652 
0653     // fixup folded ranges
0654     m_foldedFoldingRanges = newFoldedFoldingRanges;
0655 
0656     // folding changed!
0657     Q_EMIT foldingRangesChanged();
0658 
0659     // all fine, stuff done, signal emitted
0660     return true;
0661 }
0662 
0663 bool TextFolding::updateFoldedRangesForRemovedRange(TextFolding::FoldingRange *oldRange)
0664 {
0665     // folded? not interesting! we don't need to touch our m_foldedFoldingRanges vector
0666     if (oldRange->flags & Folded) {
0667         return false;
0668     }
0669 
0670     // any of the parents folded? not interesting, too!
0671     TextFolding::FoldingRange *parent = oldRange->parent;
0672     while (parent) {
0673         // parent folded => be done
0674         if (parent->flags & Folded) {
0675             return false;
0676         }
0677 
0678         // walk up
0679         parent = parent->parent;
0680     }
0681 
0682     // ok, if we arrive here, we are a unfolded range and we have no folded parent
0683     // we now want to remove this range from the m_foldedFoldingRanges vector and include our nested folded ranges!
0684     // TODO: OPTIMIZE
0685     FoldingRange::Vector newFoldedFoldingRanges;
0686     for (FoldingRange *range : std::as_const(m_foldedFoldingRanges)) {
0687         // right range? insert folded nested ranges
0688         if (range == oldRange) {
0689             appendFoldedRanges(newFoldedFoldingRanges, oldRange->nestedRanges);
0690             continue;
0691         }
0692 
0693         // just transfer range
0694         newFoldedFoldingRanges.push_back(range);
0695     }
0696 
0697     // fixup folded ranges
0698     m_foldedFoldingRanges = newFoldedFoldingRanges;
0699 
0700     // folding changed!
0701     Q_EMIT foldingRangesChanged();
0702 
0703     // all fine, stuff done, signal emitted
0704     return true;
0705 }
0706 
0707 void TextFolding::appendFoldedRanges(TextFolding::FoldingRange::Vector &newFoldedFoldingRanges, const TextFolding::FoldingRange::Vector &ranges) const
0708 {
0709     // search for folded ranges and append them
0710     for (FoldingRange *range : ranges) {
0711         // itself folded? append
0712         if (range->flags & Folded) {
0713             newFoldedFoldingRanges.push_back(range);
0714             continue;
0715         }
0716 
0717         // else: recurse!
0718         appendFoldedRanges(newFoldedFoldingRanges, range->nestedRanges);
0719     }
0720 }
0721 
0722 QJsonDocument TextFolding::exportFoldingRanges() const
0723 {
0724     QJsonObject obj;
0725     QJsonArray array;
0726     exportFoldingRanges(m_foldingRanges, array);
0727     obj.insert(QStringLiteral("ranges"), array);
0728     obj.insert(QStringLiteral("checksum"), QString::fromLocal8Bit(m_buffer.digest().toHex()));
0729     QJsonDocument folds;
0730     folds.setObject(obj);
0731     return folds;
0732 }
0733 
0734 void TextFolding::exportFoldingRanges(const TextFolding::FoldingRange::Vector &ranges, QJsonArray &folds)
0735 {
0736     // dump all ranges recursively
0737     for (FoldingRange *range : ranges) {
0738         // construct one range and dump to folds
0739         QJsonObject rangeMap;
0740         rangeMap[QStringLiteral("startLine")] = range->start->line();
0741         rangeMap[QStringLiteral("startColumn")] = range->start->column();
0742         rangeMap[QStringLiteral("endLine")] = range->end->line();
0743         rangeMap[QStringLiteral("endColumn")] = range->end->column();
0744         rangeMap[QStringLiteral("flags")] = (int)range->flags;
0745         folds.append(rangeMap);
0746 
0747         // recurse
0748         exportFoldingRanges(range->nestedRanges, folds);
0749     }
0750 }
0751 
0752 void TextFolding::importFoldingRanges(const QJsonDocument &folds)
0753 {
0754     clearFoldingRanges();
0755 
0756     const auto checksum = QByteArray::fromHex(folds.object().value(QStringLiteral("checksum")).toString().toLocal8Bit());
0757     if (checksum != m_buffer.digest()) {
0758         return;
0759     }
0760 
0761     // try to create all folding ranges
0762     const auto jsonRanges = folds.object().value(QStringLiteral("ranges")).toArray();
0763     for (const auto &rangeVariant : jsonRanges) {
0764         // get map
0765         const auto rangeMap = rangeVariant.toObject();
0766 
0767         // construct range start/end
0768         const KTextEditor::Cursor start(rangeMap[QStringLiteral("startLine")].toInt(), rangeMap[QStringLiteral("startColumn")].toInt());
0769         const KTextEditor::Cursor end(rangeMap[QStringLiteral("endLine")].toInt(), rangeMap[QStringLiteral("endColumn")].toInt());
0770 
0771         // check validity (required when loading a possibly broken folding state from disk)
0772         auto doc = m_buffer.document();
0773         if (start >= end || !doc->isValidTextPosition(start) || !doc->isValidTextPosition(end)) {
0774             continue;
0775         }
0776 
0777         // get flags
0778         const int rawFlags = rangeMap[QStringLiteral("flags")].toInt();
0779         FoldingRangeFlags flags;
0780         if (rawFlags & Persistent) {
0781             flags = Persistent;
0782         }
0783         if (rawFlags & Folded) {
0784             flags = Folded;
0785         }
0786 
0787         // create folding range
0788         newFoldingRange(KTextEditor::Range(start, end), flags);
0789     }
0790 }
0791 
0792 }
0793 
0794 #include "moc_katetextfolding.cpp"