1/*
2 SPDX-FileCopyrightText: 2010 Christoph Cullmann <cullmann@kde.org>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "katetextblock.h"
8#include "katetextbuffer.h"
9#include "katetextcursor.h"
10#include "katetextrange.h"
11
12namespace Kate
13{
14TextBlock::TextBlock(TextBuffer *buffer, int startLine)
15 : m_buffer(buffer)
16 , m_startLine(startLine)
17{
18 // reserve the block size
19 m_lines.reserve(n: BufferBlockSize);
20}
21
22TextBlock::~TextBlock()
23{
24 // blocks should be empty before they are deleted!
25 Q_ASSERT(m_blockSize == 0);
26 Q_ASSERT(m_lines.empty());
27 Q_ASSERT(m_cursors.empty());
28
29 // it only is a hint for ranges for this block, not the storage of them
30}
31
32void TextBlock::setStartLine(int startLine)
33{
34 // allow only valid lines
35 Q_ASSERT(startLine >= 0);
36 Q_ASSERT(startLine < m_buffer->lines());
37
38 m_startLine = startLine;
39}
40
41TextLine TextBlock::line(int line) const
42{
43 // right input
44 Q_ASSERT(line >= startLine());
45
46 // get text line, at will bail out on out-of-range
47 return m_lines.at(n: line - startLine());
48}
49
50void TextBlock::setLineMetaData(int line, const TextLine &textLine)
51{
52 // right input
53 Q_ASSERT(line >= startLine());
54
55 // set stuff, at will bail out on out-of-range
56 const QString originalText = m_lines.at(n: line - startLine()).text();
57 m_lines.at(n: line - startLine()) = textLine;
58 m_lines.at(n: line - startLine()).text() = originalText;
59}
60
61void TextBlock::appendLine(const QString &textOfLine)
62{
63 m_lines.emplace_back(args: textOfLine);
64 m_blockSize += textOfLine.size();
65}
66
67void TextBlock::clearLines()
68{
69 m_lines.clear();
70 m_blockSize = 0;
71}
72
73void TextBlock::text(QString &text) const
74{
75 // combine all lines
76 for (size_t i = 0; i < m_lines.size(); ++i) {
77 // not first line, insert \n
78 if (i > 0 || startLine() > 0) {
79 text.append(c: QLatin1Char('\n'));
80 }
81
82 text.append(s: m_lines.at(n: i).text());
83 }
84}
85
86void TextBlock::wrapLine(const KTextEditor::Cursor position, int fixStartLinesStartIndex)
87{
88 // calc internal line
89 int line = position.line() - startLine();
90
91 // get text, copy, we might invalidate the reference
92 const QString text = m_lines.at(n: line).text();
93
94 // check if valid column
95 Q_ASSERT(position.column() >= 0);
96 Q_ASSERT(position.column() <= text.size());
97
98 // create new line and insert it
99 m_lines.insert(position: m_lines.begin() + line + 1, x: TextLine());
100
101 // cases for modification:
102 // 1. line is wrapped in the middle
103 // 2. if empty line is wrapped, mark new line as modified
104 // 3. line-to-be-wrapped is already modified
105 if (position.column() > 0 || text.size() == 0 || m_lines.at(n: line).markedAsModified()) {
106 m_lines.at(n: line + 1).markAsModified(modified: true);
107 } else if (m_lines.at(n: line).markedAsSavedOnDisk()) {
108 m_lines.at(n: line + 1).markAsSavedOnDisk(savedOnDisk: true);
109 }
110
111 // perhaps remove some text from previous line and append it
112 if (position.column() < text.size()) {
113 // text from old line moved first to new one
114 m_lines.at(n: line + 1).text() = text.right(n: text.size() - position.column());
115
116 // now remove wrapped text from old line
117 m_lines.at(n: line).text().chop(n: text.size() - position.column());
118
119 // mark line as modified
120 m_lines.at(n: line).markAsModified(modified: true);
121 }
122
123 // fix all start lines
124 // we need to do this NOW, else the range update will FAIL!
125 // bug 313759
126 m_buffer->fixStartLines(startBlock: fixStartLinesStartIndex);
127
128 // notify the text history
129 m_buffer->history().wrapLine(position);
130
131 // cursor and range handling below
132
133 // no cursors will leave or join this block
134
135 // no cursors in this block, no work to do..
136 if (m_cursors.empty()) {
137 return;
138 }
139
140 // move all cursors on the line which has the text inserted
141 // remember all ranges modified, optimize for the standard case of a few ranges
142 QVarLengthArray<TextRange *, 32> changedRanges;
143 for (TextCursor *cursor : m_cursors) {
144 // skip cursors on lines in front of the wrapped one!
145 if (cursor->lineInBlock() < line) {
146 continue;
147 }
148
149 // either this is simple, line behind the wrapped one
150 if (cursor->lineInBlock() > line) {
151 // patch line of cursor
152 cursor->m_line++;
153 }
154
155 // this is the wrapped line
156 else {
157 // skip cursors with too small column
158 if (cursor->column() <= position.column()) {
159 if (cursor->column() < position.column() || !cursor->m_moveOnInsert) {
160 continue;
161 }
162 }
163
164 // move cursor
165
166 // patch line of cursor
167 cursor->m_line++;
168
169 // patch column
170 cursor->m_column -= position.column();
171 }
172
173 // remember range, if any, avoid double insert
174 auto range = cursor->kateRange();
175 if (range && !range->isValidityCheckRequired()) {
176 range->setValidityCheckRequired();
177 changedRanges.push_back(t: range);
178 }
179 }
180
181 // we might need to invalidate ranges or notify about their changes
182 // checkValidity might trigger delete of the range!
183 for (TextRange *range : std::as_const(t&: changedRanges)) {
184 // we need to do updateRange to ALWAYS ensure the line => range and back cache is updated
185 // see MovingRangeTest::testLineWrapOrUnwrapUpdateRangeForLineCache
186 updateRange(range);
187
188 // in addition: ensure that we really invalidate bad ranges!
189 range->checkValidity(oldLineRange: range->toLineRange());
190 }
191}
192
193void TextBlock::unwrapLine(int line, TextBlock *previousBlock, int fixStartLinesStartIndex)
194{
195 // calc internal line
196 line = line - startLine();
197
198 // two possiblities: either first line of this block or later line
199 if (line == 0) {
200 // we need previous block with at least one line
201 Q_ASSERT(previousBlock);
202 Q_ASSERT(previousBlock->lines() > 0);
203
204 // move last line of previous block to this one, might result in empty block
205 const TextLine oldFirst = m_lines.at(n: 0);
206 int lastLineOfPreviousBlock = previousBlock->lines() - 1;
207 m_lines[0] = previousBlock->m_lines.back();
208 previousBlock->m_lines.erase(position: previousBlock->m_lines.begin() + (previousBlock->lines() - 1));
209
210 const int oldSizeOfPreviousLine = m_lines[0].text().size();
211 if (oldFirst.length() > 0) {
212 // append text
213 m_lines[0].text().append(s: oldFirst.text());
214
215 // mark line as modified, since text was appended
216 m_lines[0].markAsModified(modified: true);
217 }
218
219 // patch startLine of this block
220 --m_startLine;
221
222 // fix all start lines
223 // we need to do this NOW, else the range update will FAIL!
224 // bug 313759
225 m_buffer->fixStartLines(startBlock: fixStartLinesStartIndex);
226
227 // notify the text history in advance
228 m_buffer->history().unwrapLine(line: startLine() + line, oldLineLength: oldSizeOfPreviousLine);
229
230 // cursor and range handling below
231
232 // no cursors in this block and the previous one, no work to do..
233 if (m_cursors.empty() && previousBlock->m_cursors.empty()) {
234 return;
235 }
236
237 // move all cursors because of the unwrapped line
238 // remember all ranges modified, optimize for the standard case of a few ranges
239 QVarLengthArray<TextRange *, 32> changedRanges;
240 for (TextCursor *cursor : m_cursors) {
241 // this is the unwrapped line
242 if (cursor->lineInBlock() == 0) {
243 // patch column
244 cursor->m_column += oldSizeOfPreviousLine;
245
246 // remember range, if any, avoid double insert
247 auto range = cursor->kateRange();
248 if (range && !range->isValidityCheckRequired()) {
249 range->setValidityCheckRequired();
250 changedRanges.push_back(t: range);
251 }
252 }
253 }
254
255 // move cursors of the moved line from previous block to this block now
256 for (auto it = previousBlock->m_cursors.begin(); it != previousBlock->m_cursors.end();) {
257 auto cursor = *it;
258 if (cursor->lineInBlock() == lastLineOfPreviousBlock) {
259 cursor->m_line = 0;
260 cursor->m_block = this;
261 m_cursors.insert(value: cursor);
262
263 // remember range, if any, avoid double insert
264 auto range = cursor->kateRange();
265 if (range && !range->isValidityCheckRequired()) {
266 range->setValidityCheckRequired();
267 changedRanges.push_back(t: range);
268 }
269
270 // remove from previous block
271 it = previousBlock->m_cursors.erase(i: it);
272 } else {
273 // keep in previous block
274 ++it;
275 }
276 }
277
278 // fixup the ranges that might be effected, because they moved from last line to this block
279 // we might need to invalidate ranges or notify about their changes
280 // checkValidity might trigger delete of the range!
281 for (TextRange *range : std::as_const(t&: changedRanges)) {
282 // update both blocks
283 updateRange(range);
284 previousBlock->updateRange(range);
285
286 // afterwards check validity, might delete this range!
287 range->checkValidity(oldLineRange: range->toLineRange());
288 }
289
290 // be done
291 return;
292 }
293
294 // easy: just move text to previous line and remove current one
295 const int oldSizeOfPreviousLine = m_lines.at(n: line - 1).length();
296 const int sizeOfCurrentLine = m_lines.at(n: line).length();
297 if (sizeOfCurrentLine > 0) {
298 m_lines.at(n: line - 1).text().append(s: m_lines.at(n: line).text());
299 }
300
301 const bool lineChanged = (oldSizeOfPreviousLine > 0 && m_lines.at(n: line - 1).markedAsModified())
302 || (sizeOfCurrentLine > 0 && (oldSizeOfPreviousLine > 0 || m_lines.at(n: line).markedAsModified()));
303 m_lines.at(n: line - 1).markAsModified(modified: lineChanged);
304 if (oldSizeOfPreviousLine == 0 && m_lines.at(n: line).markedAsSavedOnDisk()) {
305 m_lines.at(n: line - 1).markAsSavedOnDisk(savedOnDisk: true);
306 }
307
308 m_lines.erase(position: m_lines.begin() + line);
309
310 // fix all start lines
311 // we need to do this NOW, else the range update will FAIL!
312 // bug 313759
313 m_buffer->fixStartLines(startBlock: fixStartLinesStartIndex);
314
315 // notify the text history in advance
316 m_buffer->history().unwrapLine(line: startLine() + line, oldLineLength: oldSizeOfPreviousLine);
317
318 // cursor and range handling below
319
320 // no cursors in this block, no work to do..
321 if (m_cursors.empty()) {
322 return;
323 }
324
325 // move all cursors because of the unwrapped line
326 // remember all ranges modified, optimize for the standard case of a few ranges
327 QVarLengthArray<TextRange *, 32> changedRanges;
328 for (TextCursor *cursor : m_cursors) {
329 // skip cursors in lines in front of removed one
330 if (cursor->lineInBlock() < line) {
331 continue;
332 }
333
334 // this is the unwrapped line
335 if (cursor->lineInBlock() == line) {
336 // patch column
337 cursor->m_column += oldSizeOfPreviousLine;
338 }
339
340 // patch line of cursor
341 cursor->m_line--;
342
343 // remember range, if any, avoid double insert
344 auto range = cursor->kateRange();
345 if (range && !range->isValidityCheckRequired()) {
346 range->setValidityCheckRequired();
347 changedRanges.push_back(t: range);
348 }
349 }
350
351 // we might need to invalidate ranges or notify about their changes
352 // checkValidity might trigger delete of the range!
353 for (TextRange *range : std::as_const(t&: changedRanges)) {
354 // we need to do updateRange to ALWAYS ensure the line => range and back cache is updated
355 // see MovingRangeTest::testLineWrapOrUnwrapUpdateRangeForLineCache
356 updateRange(range);
357
358 // in addition: ensure that we really invalidate bad ranges!
359 range->checkValidity(oldLineRange: range->toLineRange());
360 }
361}
362
363void TextBlock::insertText(const KTextEditor::Cursor position, const QString &text)
364{
365 // calc internal line
366 int line = position.line() - startLine();
367
368 // get text
369 QString &textOfLine = m_lines.at(n: line).text();
370 int oldLength = textOfLine.size();
371 m_lines.at(n: line).markAsModified(modified: true);
372
373 // check if valid column
374 Q_ASSERT(position.column() >= 0);
375 Q_ASSERT(position.column() <= textOfLine.size());
376
377 // insert text
378 textOfLine.insert(i: position.column(), s: text);
379
380 // notify the text history
381 m_buffer->history().insertText(position, length: text.size(), oldLineLength: oldLength);
382
383 m_blockSize += text.size();
384
385 // cursor and range handling below
386
387 // no cursors in this block, no work to do..
388 if (m_cursors.empty()) {
389 return;
390 }
391
392 // move all cursors on the line which has the text inserted
393 // remember all ranges modified, optimize for the standard case of a few ranges
394 QVarLengthArray<TextRange *, 32> changedRanges;
395 for (TextCursor *cursor : m_cursors) {
396 // skip cursors not on this line!
397 if (cursor->lineInBlock() != line) {
398 continue;
399 }
400
401 // skip cursors with too small column
402 if (cursor->column() <= position.column()) {
403 if (cursor->column() < position.column() || !cursor->m_moveOnInsert) {
404 continue;
405 }
406 }
407
408 // patch column of cursor
409 if (cursor->m_column <= oldLength) {
410 cursor->m_column += text.size();
411 }
412
413 // special handling if cursor behind the real line, e.g. non-wrapping cursor in block selection mode
414 else if (cursor->m_column < textOfLine.size()) {
415 cursor->m_column = textOfLine.size();
416 }
417
418 // remember range, if any, avoid double insert
419 // we only need to trigger checkValidity later if the range has feedback or might be invalidated
420 auto range = cursor->kateRange();
421 if (range && !range->isValidityCheckRequired() && (range->feedback() || range->start().line() == range->end().line())) {
422 range->setValidityCheckRequired();
423 changedRanges.push_back(t: range);
424 }
425 }
426
427 // we might need to invalidate ranges or notify about their changes
428 // checkValidity might trigger delete of the range!
429 for (TextRange *range : std::as_const(t&: changedRanges)) {
430 range->checkValidity(oldLineRange: range->toLineRange());
431 }
432}
433
434void TextBlock::removeText(KTextEditor::Range range, QString &removedText)
435{
436 // calc internal line
437 int line = range.start().line() - startLine();
438
439 // get text
440 QString &textOfLine = m_lines.at(n: line).text();
441 int oldLength = textOfLine.size();
442
443 // check if valid column
444 Q_ASSERT(range.start().column() >= 0);
445 Q_ASSERT(range.start().column() <= textOfLine.size());
446 Q_ASSERT(range.end().column() >= 0);
447 Q_ASSERT(range.end().column() <= textOfLine.size());
448
449 // get text which will be removed
450 removedText = textOfLine.mid(position: range.start().column(), n: range.end().column() - range.start().column());
451
452 // remove text
453 textOfLine.remove(i: range.start().column(), len: range.end().column() - range.start().column());
454 m_lines.at(n: line).markAsModified(modified: true);
455
456 // notify the text history
457 m_buffer->history().removeText(range, oldLineLength: oldLength);
458
459 m_blockSize -= removedText.size();
460
461 // cursor and range handling below
462
463 // no cursors in this block, no work to do..
464 if (m_cursors.empty()) {
465 return;
466 }
467
468 // move all cursors on the line which has the text removed
469 // remember all ranges modified, optimize for the standard case of a few ranges
470 QVarLengthArray<TextRange *, 32> changedRanges;
471 for (TextCursor *cursor : m_cursors) {
472 // skip cursors not on this line!
473 if (cursor->lineInBlock() != line) {
474 continue;
475 }
476
477 // skip cursors with too small column
478 if (cursor->column() <= range.start().column()) {
479 continue;
480 }
481
482 // patch column of cursor
483 if (cursor->column() <= range.end().column()) {
484 cursor->m_column = range.start().column();
485 } else {
486 cursor->m_column -= (range.end().column() - range.start().column());
487 }
488
489 // remember range, if any, avoid double insert
490 // we only need to trigger checkValidity later if the range has feedback or might be invalidated
491 auto range = cursor->kateRange();
492 if (range && !range->isValidityCheckRequired() && (range->feedback() || range->start().line() == range->end().line())) {
493 range->setValidityCheckRequired();
494 changedRanges.push_back(t: range);
495 }
496 }
497
498 // we might need to invalidate ranges or notify about their changes
499 // checkValidity might trigger delete of the range!
500 for (TextRange *range : std::as_const(t&: changedRanges)) {
501 range->checkValidity(oldLineRange: range->toLineRange());
502 }
503}
504
505void TextBlock::debugPrint(int blockIndex) const
506{
507 // print all blocks
508 for (size_t i = 0; i < m_lines.size(); ++i) {
509 printf(format: "%4d - %4llu : %4llu : '%s'\n",
510 blockIndex,
511 (unsigned long long)startLine() + i,
512 (unsigned long long)m_lines.at(n: i).text().size(),
513 qPrintable(m_lines.at(i).text()));
514 }
515}
516
517TextBlock *TextBlock::splitBlock(int fromLine)
518{
519 // half the block
520 int linesOfNewBlock = lines() - fromLine;
521
522 // create and insert new block
523 TextBlock *newBlock = new TextBlock(m_buffer, startLine() + fromLine);
524
525 // move lines
526 newBlock->m_lines.reserve(n: linesOfNewBlock);
527 for (size_t i = fromLine; i < m_lines.size(); ++i) {
528 auto line = std::move(m_lines[i]);
529 m_blockSize -= line.length();
530 newBlock->m_blockSize += line.length();
531 newBlock->m_lines.push_back(x: std::move(line));
532 }
533
534 m_lines.resize(new_size: fromLine);
535
536 // move cursors
537 for (auto it = m_cursors.begin(); it != m_cursors.end();) {
538 auto cursor = *it;
539 if (cursor->lineInBlock() >= fromLine) {
540 cursor->m_line = cursor->lineInBlock() - fromLine;
541 cursor->m_block = newBlock;
542
543 // add to new, remove from current
544 newBlock->m_cursors.insert(value: cursor);
545 it = m_cursors.erase(i: it);
546 } else {
547 // keep in current
548 ++it;
549 }
550 }
551
552 // fix ALL ranges!
553 // copy is necessary as update range may modify the uncached ranges
554 std::vector<TextRange *> allRanges;
555 allRanges.reserve(n: m_uncachedRanges.size() + m_cachedLineForRanges.size());
556 std::for_each(first: m_cachedLineForRanges.keyBegin(), last: m_cachedLineForRanges.keyEnd(), f: [&allRanges](TextRange *range) {
557 allRanges.push_back(x: range);
558 });
559 allRanges.insert(position: allRanges.end(), first: m_uncachedRanges.begin(), last: m_uncachedRanges.end());
560 for (TextRange *range : allRanges) {
561 // update both blocks
562 updateRange(range);
563 newBlock->updateRange(range);
564 }
565
566 // return the new generated block
567 return newBlock;
568}
569
570void TextBlock::mergeBlock(TextBlock *targetBlock)
571{
572 // move cursors, do this first, now still lines() count is correct for target
573 for (TextCursor *cursor : m_cursors) {
574 cursor->m_line = cursor->lineInBlock() + targetBlock->lines();
575 cursor->m_block = targetBlock;
576 targetBlock->m_cursors.insert(value: cursor);
577 }
578 m_cursors.clear();
579
580 // move lines
581 targetBlock->m_lines.reserve(n: targetBlock->lines() + lines());
582 for (size_t i = 0; i < m_lines.size(); ++i) {
583 targetBlock->m_lines.push_back(x: m_lines.at(n: i));
584 }
585 targetBlock->m_blockSize += m_blockSize;
586 clearLines();
587
588 // fix ALL ranges!
589 // copy is necessary as update range may modify the uncached ranges
590 std::vector<TextRange *> allRanges;
591 allRanges.reserve(n: m_uncachedRanges.size() + m_cachedLineForRanges.size());
592 std::for_each(first: m_cachedLineForRanges.keyBegin(), last: m_cachedLineForRanges.keyEnd(), f: [&allRanges](TextRange *range) {
593 allRanges.push_back(x: range);
594 });
595 allRanges.insert(position: allRanges.end(), first: m_uncachedRanges.begin(), last: m_uncachedRanges.end());
596 for (TextRange *range : allRanges) {
597 // update both blocks
598 updateRange(range);
599 targetBlock->updateRange(range);
600 }
601}
602
603void TextBlock::deleteBlockContent()
604{
605 // kill cursors, if not belonging to a range
606 // we can do in-place editing of the current set of cursors as
607 // we remove them before deleting
608 for (auto it = m_cursors.begin(); it != m_cursors.end();) {
609 auto cursor = *it;
610 if (!cursor->kateRange()) {
611 // remove it and advance to next element
612 it = m_cursors.erase(i: it);
613
614 // delete after cursor is gone from the set
615 // else the destructor will modify it!
616 delete cursor;
617 } else {
618 // keep this cursor
619 ++it;
620 }
621 }
622
623 // kill lines
624 clearLines();
625}
626
627void TextBlock::clearBlockContent(TextBlock *targetBlock)
628{
629 // move cursors, if not belonging to a range
630 // we can do in-place editing of the current set of cursors
631 for (auto it = m_cursors.begin(); it != m_cursors.end();) {
632 auto cursor = *it;
633 if (!cursor->kateRange()) {
634 cursor->m_column = 0;
635 cursor->m_line = 0;
636 cursor->m_block = targetBlock;
637 targetBlock->m_cursors.insert(value: cursor);
638
639 // remove it and advance to next element
640 it = m_cursors.erase(i: it);
641 } else {
642 // keep this cursor
643 ++it;
644 }
645 }
646
647 // kill lines
648 clearLines();
649}
650
651QList<TextRange *> TextBlock::rangesForLine(int line, KTextEditor::View *view, bool rangesWithAttributeOnly) const
652{
653 const auto cachedRanges = cachedRangesForLine(line);
654 QList<TextRange *> ranges;
655 ranges.reserve(asize: m_uncachedRanges.size() + (cachedRanges ? cachedRanges->size() : 0));
656 rangesForLine(line, view, rangesWithAttributeOnly, outRanges&: ranges);
657 return ranges;
658}
659
660void TextBlock::rangesForLine(int line, KTextEditor::View *view, bool rangesWithAttributeOnly, QList<TextRange *> &outRanges) const
661{
662 const auto cachedRanges = cachedRangesForLine(line);
663 outRanges.clear();
664
665 auto predicate = [line, view, rangesWithAttributeOnly](TextRange *range) {
666 if (rangesWithAttributeOnly && !range->hasAttribute()) {
667 return false;
668 }
669
670 // we want ranges for no view, but this one's attribute is only valid for views
671 if (!view && range->attributeOnlyForViews()) {
672 return false;
673 }
674
675 // the range's attribute is not valid for this view
676 if (range->view() && range->view() != view) {
677 return false;
678 }
679
680 // if line is in the range, ok
681 if (range->startInternal().lineInternal() <= line && line <= range->endInternal().lineInternal()) {
682 return true;
683 }
684 return false;
685 };
686
687 if (cachedRanges) {
688 std::copy_if(first: cachedRanges->begin(), last: cachedRanges->end(), result: std::back_inserter(x&: outRanges), pred: predicate);
689 }
690 std::copy_if(first: m_uncachedRanges.begin(), last: m_uncachedRanges.end(), result: std::back_inserter(x&: outRanges), pred: predicate);
691}
692
693void TextBlock::markModifiedLinesAsSaved()
694{
695 // mark all modified lines as saved
696 for (auto &textLine : m_lines) {
697 if (textLine.markedAsModified()) {
698 textLine.markAsSavedOnDisk(savedOnDisk: true);
699 }
700 }
701}
702
703void TextBlock::updateRange(TextRange *range)
704{
705 // get some simple facts about our nice range
706 const int startLine = range->startInternal().lineInternal();
707 const int endLine = range->endInternal().lineInternal();
708 const bool isSingleLine = startLine == endLine;
709
710 // perhaps remove range and be done
711 if ((endLine < m_startLine) || (startLine >= (m_startLine + lines()))) {
712 removeRange(range);
713 return;
714 }
715
716 // The range is still a single-line range, and is still cached to the correct line.
717 if (isSingleLine) {
718 auto it = m_cachedLineForRanges.find(key: range);
719 if (it != m_cachedLineForRanges.end() && it.value() == startLine - m_startLine) {
720 return;
721 }
722 }
723
724 // The range is still a multi-line range, and is already in the correct set.
725 if (!isSingleLine && m_uncachedRanges.contains(t: range)) {
726 return;
727 }
728
729 // remove, if already there!
730 removeRange(range);
731
732 // simple case: multi-line range
733 if (!isSingleLine) {
734 // The range cannot be cached per line, as it spans multiple lines
735 m_uncachedRanges.append(t: range);
736 return;
737 }
738
739 // The range is contained by a single line, put it into the line-cache
740 const int lineOffset = startLine - m_startLine;
741
742 // enlarge cache if needed
743 if (m_cachedRangesForLine.size() <= (size_t)lineOffset) {
744 m_cachedRangesForLine.resize(new_size: lineOffset + 1);
745 }
746
747 // insert into mapping
748 if (!m_cachedRangesForLine[lineOffset].contains(t: range)) {
749 m_cachedRangesForLine[lineOffset].push_back(t: range);
750 }
751 m_cachedLineForRanges[range] = lineOffset;
752}
753
754void TextBlock::removeRange(TextRange *range)
755{
756 // uncached range? remove it and be done
757 int pos = m_uncachedRanges.indexOf(t: range);
758 if (pos != -1) {
759 m_uncachedRanges.remove(i: pos);
760 // must be only uncached!
761 Q_ASSERT(m_cachedLineForRanges.find(range) == m_cachedLineForRanges.end());
762 return;
763 }
764
765 // cached range?
766 auto it = m_cachedLineForRanges.find(key: range);
767 if (it != m_cachedLineForRanges.end()) {
768 // must be only cached!
769 Q_ASSERT(!m_uncachedRanges.contains(range));
770
771 int line = it.value();
772
773 // query the range from cache, must be there
774 int idx = m_cachedRangesForLine[line].indexOf(t: range);
775 Q_ASSERT(idx != -1);
776
777 // remove it and be done
778 m_cachedRangesForLine[line].remove(i: idx);
779 m_cachedLineForRanges.erase(it);
780 return;
781 }
782
783 // else: range was not for this block, just do nothing, removeRange should be "safe" to use
784}
785
786}
787

source code of ktexteditor/src/buffer/katetextblock.cpp