Warning, file /frameworks/ktexteditor/src/buffer/katetextfolding.cpp was not indexed or was modified since last indexation (in which case cross-reference links may be missing, inaccurate or erroneous).

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