Warning, file /frameworks/ktexteditor/src/buffer/katetextblock.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: 2010 Christoph Cullmann <cullmann@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "katetextblock.h"
0008 #include "katetextbuffer.h"
0009 #include "katetextcursor.h"
0010 #include "katetextrange.h"
0011 
0012 
0013 namespace Kate
0014 {
0015 TextBlock::TextBlock(TextBuffer *buffer, int startLine)
0016     : m_buffer(buffer)
0017     , m_startLine(startLine)
0018 {
0019     // reserve the block size
0020     m_lines.reserve(m_buffer->m_blockSize);
0021 }
0022 
0023 TextBlock::~TextBlock()
0024 {
0025     // blocks should be empty before they are deleted!
0026     Q_ASSERT(m_lines.empty());
0027     Q_ASSERT(m_cursors.empty());
0028 
0029     // it only is a hint for ranges for this block, not the storage of them
0030 }
0031 
0032 void TextBlock::setStartLine(int startLine)
0033 {
0034     // allow only valid lines
0035     Q_ASSERT(startLine >= 0);
0036     Q_ASSERT(startLine < m_buffer->lines());
0037 
0038     m_startLine = startLine;
0039 }
0040 
0041 TextLine TextBlock::line(int line) const
0042 {
0043     // right input
0044     Q_ASSERT(line >= startLine());
0045 
0046     // get text line, at will bail out on out-of-range
0047     return m_lines.at(line - startLine());
0048 }
0049 
0050 void TextBlock::appendLine(const QString &textOfLine)
0051 {
0052     m_lines.push_back(std::make_shared<Kate::TextLineData>(textOfLine));
0053 }
0054 
0055 void TextBlock::clearLines()
0056 {
0057     m_lines.clear();
0058 }
0059 
0060 void TextBlock::text(QString &text) const
0061 {
0062     // combine all lines
0063     for (size_t i = 0; i < m_lines.size(); ++i) {
0064         // not first line, insert \n
0065         if (i > 0 || startLine() > 0) {
0066             text.append(QLatin1Char('\n'));
0067         }
0068 
0069         text.append(m_lines.at(i)->text());
0070     }
0071 }
0072 
0073 void TextBlock::wrapLine(const KTextEditor::Cursor position, int fixStartLinesStartIndex)
0074 {
0075     // calc internal line
0076     int line = position.line() - startLine();
0077 
0078     // get text
0079     QString &text = m_lines.at(line)->textReadWrite();
0080 
0081     // check if valid column
0082     Q_ASSERT(position.column() >= 0);
0083     Q_ASSERT(position.column() <= text.size());
0084 
0085     // create new line and insert it
0086     m_lines.insert(m_lines.begin() + line + 1, TextLine(new TextLineData()));
0087 
0088     // cases for modification:
0089     // 1. line is wrapped in the middle
0090     // 2. if empty line is wrapped, mark new line as modified
0091     // 3. line-to-be-wrapped is already modified
0092     if (position.column() > 0 || text.size() == 0 || m_lines.at(line)->markedAsModified()) {
0093         m_lines.at(line + 1)->markAsModified(true);
0094     } else if (m_lines.at(line)->markedAsSavedOnDisk()) {
0095         m_lines.at(line + 1)->markAsSavedOnDisk(true);
0096     }
0097 
0098     // perhaps remove some text from previous line and append it
0099     if (position.column() < text.size()) {
0100         // text from old line moved first to new one
0101         m_lines.at(line + 1)->textReadWrite() = text.right(text.size() - position.column());
0102 
0103         // now remove wrapped text from old line
0104         text.chop(text.size() - position.column());
0105 
0106         // mark line as modified
0107         m_lines.at(line)->markAsModified(true);
0108     }
0109 
0110     // fix all start lines
0111     // we need to do this NOW, else the range update will FAIL!
0112     // bug 313759
0113     m_buffer->fixStartLines(fixStartLinesStartIndex);
0114 
0115     // notify the text history
0116     m_buffer->history().wrapLine(position);
0117 
0118     // cursor and range handling below
0119 
0120     // no cursors will leave or join this block
0121 
0122     // no cursors in this block, no work to do..
0123     if (m_cursors.empty()) {
0124         return;
0125     }
0126 
0127     // move all cursors on the line which has the text inserted
0128     // remember all ranges modified, optimize for the standard case of a few ranges
0129     QVarLengthArray<TextRange *, 32> changedRanges;
0130     for (TextCursor *cursor : m_cursors) {
0131         // skip cursors on lines in front of the wrapped one!
0132         if (cursor->lineInBlock() < line) {
0133             continue;
0134         }
0135 
0136         // either this is simple, line behind the wrapped one
0137         if (cursor->lineInBlock() > line) {
0138             // patch line of cursor
0139             cursor->m_line++;
0140         }
0141 
0142         // this is the wrapped line
0143         else {
0144             // skip cursors with too small column
0145             if (cursor->column() <= position.column()) {
0146                 if (cursor->column() < position.column() || !cursor->m_moveOnInsert) {
0147                     continue;
0148                 }
0149             }
0150 
0151             // move cursor
0152 
0153             // patch line of cursor
0154             cursor->m_line++;
0155 
0156             // patch column
0157             cursor->m_column -= position.column();
0158         }
0159 
0160         // remember range, if any, avoid double insert
0161         auto range = cursor->kateRange();
0162         if (range && !range->isValidityCheckRequired()) {
0163             range->setValidityCheckRequired();
0164             changedRanges.push_back(range);
0165         }
0166     }
0167 
0168     // we might need to invalidate ranges or notify about their changes
0169     // checkValidity might trigger delete of the range!
0170     for (TextRange *range : std::as_const(changedRanges)) {
0171         // we need to do updateRange to ALWAYS ensure the line => range and back cache is updated
0172         // see MovingRangeTest::testLineWrapOrUnwrapUpdateRangeForLineCache
0173         updateRange(range);
0174 
0175         // in addition: ensure that we really invalidate bad ranges!
0176         range->checkValidity(range->toLineRange());
0177     }
0178 }
0179 
0180 void TextBlock::unwrapLine(int line, TextBlock *previousBlock, int fixStartLinesStartIndex)
0181 {
0182     // calc internal line
0183     line = line - startLine();
0184 
0185     // two possiblities: either first line of this block or later line
0186     if (line == 0) {
0187         // we need previous block with at least one line
0188         Q_ASSERT(previousBlock);
0189         Q_ASSERT(previousBlock->lines() > 0);
0190 
0191         // move last line of previous block to this one, might result in empty block
0192         TextLine oldFirst = m_lines.at(0);
0193         int lastLineOfPreviousBlock = previousBlock->lines() - 1;
0194         TextLine newFirst = previousBlock->m_lines.back();
0195         m_lines[0] = newFirst;
0196         previousBlock->m_lines.erase(previousBlock->m_lines.begin() + (previousBlock->lines() - 1));
0197 
0198         const int oldSizeOfPreviousLine = newFirst->text().size();
0199         if (oldFirst->length() > 0) {
0200             // append text
0201             newFirst->textReadWrite().append(oldFirst->text());
0202 
0203             // mark line as modified, since text was appended
0204             newFirst->markAsModified(true);
0205         }
0206 
0207         // patch startLine of this block
0208         --m_startLine;
0209 
0210         // fix all start lines
0211         // we need to do this NOW, else the range update will FAIL!
0212         // bug 313759
0213         m_buffer->fixStartLines(fixStartLinesStartIndex);
0214 
0215         // notify the text history in advance
0216         m_buffer->history().unwrapLine(startLine() + line, oldSizeOfPreviousLine);
0217 
0218         // cursor and range handling below
0219 
0220         // no cursors in this block and the previous one, no work to do..
0221         if (m_cursors.empty() && previousBlock->m_cursors.empty()) {
0222             return;
0223         }
0224 
0225         // move all cursors because of the unwrapped line
0226         // remember all ranges modified, optimize for the standard case of a few ranges
0227         QVarLengthArray<TextRange *, 32> changedRanges;
0228         for (TextCursor *cursor : m_cursors) {
0229             // this is the unwrapped line
0230             if (cursor->lineInBlock() == 0) {
0231                 // patch column
0232                 cursor->m_column += oldSizeOfPreviousLine;
0233 
0234                 // remember range, if any, avoid double insert
0235                 auto range = cursor->kateRange();
0236                 if (range && !range->isValidityCheckRequired()) {
0237                     range->setValidityCheckRequired();
0238                     changedRanges.push_back(range);
0239                 }
0240             }
0241         }
0242 
0243         // move cursors of the moved line from previous block to this block now
0244         for (auto it = previousBlock->m_cursors.begin(); it != previousBlock->m_cursors.end();) {
0245             auto cursor = *it;
0246             if (cursor->lineInBlock() == lastLineOfPreviousBlock) {
0247                 cursor->m_line = 0;
0248                 cursor->m_block = this;
0249                 m_cursors.insert(cursor);
0250 
0251                 // remember range, if any, avoid double insert
0252                 auto range = cursor->kateRange();
0253                 if (range && !range->isValidityCheckRequired()) {
0254                     range->setValidityCheckRequired();
0255                     changedRanges.push_back(range);
0256                 }
0257 
0258                 // remove from previous block
0259                 it = previousBlock->m_cursors.erase(it);
0260             } else {
0261                 // keep in previous block
0262                 ++it;
0263             }
0264         }
0265 
0266         // fixup the ranges that might be effected, because they moved from last line to this block
0267         // we might need to invalidate ranges or notify about their changes
0268         // checkValidity might trigger delete of the range!
0269         for (TextRange *range : std::as_const(changedRanges)) {
0270             // update both blocks
0271             updateRange(range);
0272             previousBlock->updateRange(range);
0273 
0274             // afterwards check validity, might delete this range!
0275             range->checkValidity(range->toLineRange());
0276         }
0277 
0278         // be done
0279         return;
0280     }
0281 
0282     // easy: just move text to previous line and remove current one
0283     const int oldSizeOfPreviousLine = m_lines.at(line - 1)->length();
0284     const int sizeOfCurrentLine = m_lines.at(line)->length();
0285     if (sizeOfCurrentLine > 0) {
0286         m_lines.at(line - 1)->textReadWrite().append(m_lines.at(line)->text());
0287     }
0288 
0289     const bool lineChanged = (oldSizeOfPreviousLine > 0 && m_lines.at(line - 1)->markedAsModified())
0290         || (sizeOfCurrentLine > 0 && (oldSizeOfPreviousLine > 0 || m_lines.at(line)->markedAsModified()));
0291     m_lines.at(line - 1)->markAsModified(lineChanged);
0292     if (oldSizeOfPreviousLine == 0 && m_lines.at(line)->markedAsSavedOnDisk()) {
0293         m_lines.at(line - 1)->markAsSavedOnDisk(true);
0294     }
0295 
0296     m_lines.erase(m_lines.begin() + line);
0297 
0298     // fix all start lines
0299     // we need to do this NOW, else the range update will FAIL!
0300     // bug 313759
0301     m_buffer->fixStartLines(fixStartLinesStartIndex);
0302 
0303     // notify the text history in advance
0304     m_buffer->history().unwrapLine(startLine() + line, oldSizeOfPreviousLine);
0305 
0306     // cursor and range handling below
0307 
0308     // no cursors in this block, no work to do..
0309     if (m_cursors.empty()) {
0310         return;
0311     }
0312 
0313     // move all cursors because of the unwrapped line
0314     // remember all ranges modified, optimize for the standard case of a few ranges
0315     QVarLengthArray<TextRange *, 32> changedRanges;
0316     for (TextCursor *cursor : m_cursors) {
0317         // skip cursors in lines in front of removed one
0318         if (cursor->lineInBlock() < line) {
0319             continue;
0320         }
0321 
0322         // this is the unwrapped line
0323         if (cursor->lineInBlock() == line) {
0324             // patch column
0325             cursor->m_column += oldSizeOfPreviousLine;
0326         }
0327 
0328         // patch line of cursor
0329         cursor->m_line--;
0330 
0331         // remember range, if any, avoid double insert
0332         auto range = cursor->kateRange();
0333         if (range && !range->isValidityCheckRequired()) {
0334             range->setValidityCheckRequired();
0335             changedRanges.push_back(range);
0336         }
0337     }
0338 
0339     // we might need to invalidate ranges or notify about their changes
0340     // checkValidity might trigger delete of the range!
0341     for (TextRange *range : std::as_const(changedRanges)) {
0342         // we need to do updateRange to ALWAYS ensure the line => range and back cache is updated
0343         // see MovingRangeTest::testLineWrapOrUnwrapUpdateRangeForLineCache
0344         updateRange(range);
0345 
0346         // in addition: ensure that we really invalidate bad ranges!
0347         range->checkValidity(range->toLineRange());
0348     }
0349 }
0350 
0351 void TextBlock::insertText(const KTextEditor::Cursor position, const QString &text)
0352 {
0353     // calc internal line
0354     int line = position.line() - startLine();
0355 
0356     // get text
0357     QString &textOfLine = m_lines.at(line)->textReadWrite();
0358     int oldLength = textOfLine.size();
0359     m_lines.at(line)->markAsModified(true);
0360 
0361     // check if valid column
0362     Q_ASSERT(position.column() >= 0);
0363     Q_ASSERT(position.column() <= textOfLine.size());
0364 
0365     // insert text
0366     textOfLine.insert(position.column(), text);
0367 
0368     // notify the text history
0369     m_buffer->history().insertText(position, text.size(), oldLength);
0370 
0371     // cursor and range handling below
0372 
0373     // no cursors in this block, no work to do..
0374     if (m_cursors.empty()) {
0375         return;
0376     }
0377 
0378     // move all cursors on the line which has the text inserted
0379     // remember all ranges modified, optimize for the standard case of a few ranges
0380     QVarLengthArray<TextRange *, 32> changedRanges;
0381     for (TextCursor *cursor : m_cursors) {
0382         // skip cursors not on this line!
0383         if (cursor->lineInBlock() != line) {
0384             continue;
0385         }
0386 
0387         // skip cursors with too small column
0388         if (cursor->column() <= position.column()) {
0389             if (cursor->column() < position.column() || !cursor->m_moveOnInsert) {
0390                 continue;
0391             }
0392         }
0393 
0394         // patch column of cursor
0395         if (cursor->m_column <= oldLength) {
0396             cursor->m_column += text.size();
0397         }
0398 
0399         // special handling if cursor behind the real line, e.g. non-wrapping cursor in block selection mode
0400         else if (cursor->m_column < textOfLine.size()) {
0401             cursor->m_column = textOfLine.size();
0402         }
0403 
0404         // remember range, if any, avoid double insert
0405         // we only need to trigger checkValidity later if the range has feedback or might be invalidated
0406         auto range = cursor->kateRange();
0407         if (range && !range->isValidityCheckRequired() && (range->feedback() || range->start().line() == range->end().line())) {
0408             range->setValidityCheckRequired();
0409             changedRanges.push_back(range);
0410         }
0411     }
0412 
0413     // we might need to invalidate ranges or notify about their changes
0414     // checkValidity might trigger delete of the range!
0415     for (TextRange *range : std::as_const(changedRanges)) {
0416         range->checkValidity(range->toLineRange());
0417     }
0418 }
0419 
0420 void TextBlock::removeText(KTextEditor::Range range, QString &removedText)
0421 {
0422     // calc internal line
0423     int line = range.start().line() - startLine();
0424 
0425     // get text
0426     QString &textOfLine = m_lines.at(line)->textReadWrite();
0427     int oldLength = textOfLine.size();
0428 
0429     // check if valid column
0430     Q_ASSERT(range.start().column() >= 0);
0431     Q_ASSERT(range.start().column() <= textOfLine.size());
0432     Q_ASSERT(range.end().column() >= 0);
0433     Q_ASSERT(range.end().column() <= textOfLine.size());
0434 
0435     // get text which will be removed
0436     removedText = textOfLine.mid(range.start().column(), range.end().column() - range.start().column());
0437 
0438     // remove text
0439     textOfLine.remove(range.start().column(), range.end().column() - range.start().column());
0440     m_lines.at(line)->markAsModified(true);
0441 
0442     // notify the text history
0443     m_buffer->history().removeText(range, oldLength);
0444 
0445     // cursor and range handling below
0446 
0447     // no cursors in this block, no work to do..
0448     if (m_cursors.empty()) {
0449         return;
0450     }
0451 
0452     // move all cursors on the line which has the text removed
0453     // remember all ranges modified, optimize for the standard case of a few ranges
0454     QVarLengthArray<TextRange *, 32> changedRanges;
0455     for (TextCursor *cursor : m_cursors) {
0456         // skip cursors not on this line!
0457         if (cursor->lineInBlock() != line) {
0458             continue;
0459         }
0460 
0461         // skip cursors with too small column
0462         if (cursor->column() <= range.start().column()) {
0463             continue;
0464         }
0465 
0466         // patch column of cursor
0467         if (cursor->column() <= range.end().column()) {
0468             cursor->m_column = range.start().column();
0469         } else {
0470             cursor->m_column -= (range.end().column() - range.start().column());
0471         }
0472 
0473         // remember range, if any, avoid double insert
0474         // we only need to trigger checkValidity later if the range has feedback or might be invalidated
0475         auto range = cursor->kateRange();
0476         if (range && !range->isValidityCheckRequired() && (range->feedback() || range->start().line() == range->end().line())) {
0477             range->setValidityCheckRequired();
0478             changedRanges.push_back(range);
0479         }
0480     }
0481 
0482     // we might need to invalidate ranges or notify about their changes
0483     // checkValidity might trigger delete of the range!
0484     for (TextRange *range : std::as_const(changedRanges)) {
0485         range->checkValidity(range->toLineRange());
0486     }
0487 }
0488 
0489 void TextBlock::debugPrint(int blockIndex) const
0490 {
0491     // print all blocks
0492     for (size_t i = 0; i < m_lines.size(); ++i) {
0493         printf("%4d - %4lld : %4d : '%s'\n", blockIndex, (unsigned long long)startLine() + i, m_lines.at(i)->text().size(), qPrintable(m_lines.at(i)->text()));
0494     }
0495 }
0496 
0497 TextBlock *TextBlock::splitBlock(int fromLine)
0498 {
0499     // half the block
0500     int linesOfNewBlock = lines() - fromLine;
0501 
0502     // create and insert new block
0503     TextBlock *newBlock = new TextBlock(m_buffer, startLine() + fromLine);
0504 
0505     // move lines
0506     newBlock->m_lines.reserve(linesOfNewBlock);
0507     for (size_t i = fromLine; i < m_lines.size(); ++i) {
0508         newBlock->m_lines.push_back(m_lines.at(i));
0509     }
0510     m_lines.resize(fromLine);
0511 
0512     // move cursors
0513     for (auto it = m_cursors.begin(); it != m_cursors.end();) {
0514         auto cursor = *it;
0515         if (cursor->lineInBlock() >= fromLine) {
0516             cursor->m_line = cursor->lineInBlock() - fromLine;
0517             cursor->m_block = newBlock;
0518 
0519             // add to new, remove from current
0520             newBlock->m_cursors.insert(cursor);
0521             it = m_cursors.erase(it);
0522         } else {
0523             // keep in current
0524             ++it;
0525         }
0526     }
0527 
0528     // fix ALL ranges!
0529     // copy is necessary as update range may modify the uncached ranges
0530     std::vector<TextRange *> allRanges;
0531     allRanges.reserve(m_uncachedRanges.size() + m_cachedLineForRanges.size());
0532     std::for_each(m_cachedLineForRanges.begin(), m_cachedLineForRanges.end(), [&allRanges](const std::pair<TextRange *, int> &pair) {
0533         allRanges.push_back(pair.first);
0534     });
0535     allRanges.insert(allRanges.end(), m_uncachedRanges.begin(), m_uncachedRanges.end());
0536     for (TextRange *range : allRanges) {
0537         // update both blocks
0538         updateRange(range);
0539         newBlock->updateRange(range);
0540     }
0541 
0542     // return the new generated block
0543     return newBlock;
0544 }
0545 
0546 void TextBlock::mergeBlock(TextBlock *targetBlock)
0547 {
0548     // move cursors, do this first, now still lines() count is correct for target
0549     for (TextCursor *cursor : m_cursors) {
0550         cursor->m_line = cursor->lineInBlock() + targetBlock->lines();
0551         cursor->m_block = targetBlock;
0552         targetBlock->m_cursors.insert(cursor);
0553     }
0554     m_cursors.clear();
0555 
0556     // move lines
0557     targetBlock->m_lines.reserve(targetBlock->lines() + lines());
0558     for (size_t i = 0; i < m_lines.size(); ++i) {
0559         targetBlock->m_lines.push_back(m_lines.at(i));
0560     }
0561     m_lines.clear();
0562 
0563     // fix ALL ranges!
0564     // copy is necessary as update range may modify the uncached ranges
0565     std::vector<TextRange *> allRanges;
0566     allRanges.reserve(m_uncachedRanges.size() + m_cachedLineForRanges.size());
0567     std::for_each(m_cachedLineForRanges.begin(), m_cachedLineForRanges.end(), [&allRanges](const std::pair<TextRange *, int> &pair) {
0568         allRanges.push_back(pair.first);
0569     });
0570     allRanges.insert(allRanges.end(), m_uncachedRanges.begin(), m_uncachedRanges.end());
0571     for (TextRange *range : allRanges) {
0572         // update both blocks
0573         updateRange(range);
0574         targetBlock->updateRange(range);
0575     }
0576 }
0577 
0578 void TextBlock::deleteBlockContent()
0579 {
0580     // kill cursors, if not belonging to a range
0581     // we can do in-place editing of the current set of cursors as
0582     // we remove them before deleting
0583     for (auto it = m_cursors.begin(); it != m_cursors.end();) {
0584         auto cursor = *it;
0585         if (!cursor->kateRange()) {
0586             // remove it and advance to next element
0587             it = m_cursors.erase(it);
0588 
0589             // delete after cursor is gone from the set
0590             // else the destructor will modify it!
0591             delete cursor;
0592         } else {
0593             // keep this cursor
0594             ++it;
0595         }
0596     }
0597 
0598     // kill lines
0599     m_lines.clear();
0600 }
0601 
0602 void TextBlock::clearBlockContent(TextBlock *targetBlock)
0603 {
0604     // move cursors, if not belonging to a range
0605     // we can do in-place editing of the current set of cursors
0606     for (auto it = m_cursors.begin(); it != m_cursors.end();) {
0607         auto cursor = *it;
0608         if (!cursor->kateRange()) {
0609             cursor->m_column = 0;
0610             cursor->m_line = 0;
0611             cursor->m_block = targetBlock;
0612             targetBlock->m_cursors.insert(cursor);
0613 
0614             // remove it and advance to next element
0615             it = m_cursors.erase(it);
0616         } else {
0617             // keep this cursor
0618             ++it;
0619         }
0620     }
0621 
0622     // kill lines
0623     m_lines.clear();
0624 }
0625 
0626 QVector<TextRange *> TextBlock::rangesForLine(int line, KTextEditor::View *view, bool rangesWithAttributeOnly) const
0627 {
0628     const auto cachedRanges = cachedRangesForLine(line);
0629     QVector<TextRange *> ranges;
0630     ranges.reserve(m_uncachedRanges.size() + cachedRanges.size());
0631 
0632     auto predicate = [line, view, rangesWithAttributeOnly](TextRange *range) {
0633         if (rangesWithAttributeOnly && !range->hasAttribute()) {
0634             return false;
0635         }
0636 
0637         // we want ranges for no view, but this one's attribute is only valid for views
0638         if (!view && range->attributeOnlyForViews()) {
0639             return false;
0640         }
0641 
0642         // the range's attribute is not valid for this view
0643         if (range->view() && range->view() != view) {
0644             return false;
0645         }
0646 
0647         // if line is in the range, ok
0648         if (range->startInternal().lineInternal() <= line && line <= range->endInternal().lineInternal()) {
0649             return true;
0650         }
0651         return false;
0652     };
0653 
0654     std::copy_if(cachedRanges.begin(), cachedRanges.end(), std::back_inserter(ranges), predicate);
0655     std::copy_if(m_uncachedRanges.begin(), m_uncachedRanges.end(), std::back_inserter(ranges), predicate);
0656     return ranges;
0657 }
0658 
0659 void TextBlock::markModifiedLinesAsSaved()
0660 {
0661     // mark all modified lines as saved
0662     for (auto &textLine : m_lines) {
0663         if (textLine->markedAsModified()) {
0664             textLine->markAsSavedOnDisk(true);
0665         }
0666     }
0667 }
0668 
0669 void TextBlock::updateRange(TextRange *range)
0670 {
0671     // get some simple facts about our nice range
0672     const int startLine = range->startInternal().lineInternal();
0673     const int endLine = range->endInternal().lineInternal();
0674     const bool isSingleLine = startLine == endLine;
0675 
0676     // perhaps remove range and be done
0677     if ((endLine < m_startLine) || (startLine >= (m_startLine + lines()))) {
0678         removeRange(range);
0679         return;
0680     }
0681 
0682     // The range is still a single-line range, and is still cached to the correct line.
0683     if (isSingleLine) {
0684         auto it = m_cachedLineForRanges.find(range);
0685         if (it != m_cachedLineForRanges.end() && it->second == startLine - m_startLine) {
0686             return;
0687         }
0688     }
0689 
0690     // The range is still a multi-line range, and is already in the correct set.
0691     if (!isSingleLine && m_uncachedRanges.contains(range)) {
0692         return;
0693     }
0694 
0695     // remove, if already there!
0696     removeRange(range);
0697 
0698     // simple case: multi-line range
0699     if (!isSingleLine) {
0700         // The range cannot be cached per line, as it spans multiple lines
0701         m_uncachedRanges.append(range);
0702         return;
0703     }
0704 
0705     // The range is contained by a single line, put it into the line-cache
0706     const int lineOffset = startLine - m_startLine;
0707 
0708     // enlarge cache if needed
0709     if (m_cachedRangesForLine.size() <= (size_t)lineOffset) {
0710         m_cachedRangesForLine.resize(lineOffset + 1);
0711     }
0712 
0713     // insert into mapping
0714     m_cachedRangesForLine[lineOffset].insert(range);
0715     m_cachedLineForRanges[range] = lineOffset;
0716 }
0717 
0718 void TextBlock::removeRange(TextRange *range)
0719 {
0720     // uncached range? remove it and be done
0721     int pos = m_uncachedRanges.indexOf(range);
0722     if (pos != -1) {
0723         m_uncachedRanges.remove(pos);
0724         // must be only uncached!
0725         Q_ASSERT(m_cachedLineForRanges.find(range) == m_cachedLineForRanges.end());
0726         return;
0727     }
0728 
0729     // cached range?
0730     auto it = m_cachedLineForRanges.find(range);
0731     if (it != m_cachedLineForRanges.end()) {
0732         // must be only cached!
0733         Q_ASSERT(!m_uncachedRanges.contains(range));
0734 
0735         int line = it->second;
0736 
0737         // query the range from cache, must be there
0738         Q_ASSERT(m_cachedRangesForLine.at(line).contains(range));
0739 
0740         // remove it and be done
0741         m_cachedRangesForLine[line].remove(range);
0742         m_cachedLineForRanges.erase(it);
0743         return;
0744     }
0745 
0746     // else: range was not for this block, just do nothing, removeRange should be "safe" to use
0747 }
0748 
0749 }