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

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