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 }