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