File indexing completed on 2024-09-08 03:40:37
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"