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 | |
12 | namespace Kate |
13 | { |
14 | TextBlock::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 | |
22 | TextBlock::~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 | |
32 | void 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 | |
41 | TextLine 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 | |
50 | void 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 | |
61 | void TextBlock::appendLine(const QString &textOfLine) |
62 | { |
63 | m_lines.emplace_back(args: textOfLine); |
64 | m_blockSize += textOfLine.size(); |
65 | } |
66 | |
67 | void TextBlock::clearLines() |
68 | { |
69 | m_lines.clear(); |
70 | m_blockSize = 0; |
71 | } |
72 | |
73 | void 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 | |
86 | void 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 | |
193 | void 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 | |
363 | void 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 | |
434 | void 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 | |
505 | void 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 | |
517 | TextBlock *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 | |
570 | void 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 | |
603 | void 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 | |
627 | void 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 | |
651 | QList<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 | |
660 | void TextBlock::(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 | |
693 | void 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 | |
703 | void TextBlock::(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 | |
754 | void TextBlock::(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 | |