File indexing completed on 2023-09-24 04:11:28
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"