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 index)
15 : m_buffer(buffer)
16 , m_blockIndex(index)
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_lines.empty());
26 Q_ASSERT(m_cursors.empty());
27
28 // it only is a hint for ranges for this block, not the storage of them
29}
30
31int TextBlock::startLine() const
32{
33 return m_buffer->m_startLines[m_blockIndex];
34}
35
36TextLine TextBlock::line(int line) const
37{
38 // right input
39 Q_ASSERT(size_t(line) < m_lines.size());
40 // get text line, at will bail out on out-of-range
41 return m_lines.at(n: line);
42}
43
44void TextBlock::setLineMetaData(int line, const TextLine &textLine)
45{
46 // right input
47 Q_ASSERT(size_t(line) < m_lines.size());
48
49 // set stuff, at will bail out on out-of-range
50 const QString originalText = m_lines.at(n: line).text();
51 m_lines.at(n: line) = textLine;
52 m_lines.at(n: line).text() = originalText;
53}
54
55void TextBlock::appendLine(const QString &textOfLine)
56{
57 m_lines.emplace_back(args: textOfLine);
58}
59
60void TextBlock::clearLines()
61{
62 m_lines.clear();
63}
64
65void TextBlock::text(QString &text) const
66{
67 // combine all lines
68 for (const auto &line : m_lines) {
69 text.append(s: line.text());
70 text.append(c: QLatin1Char('\n'));
71 }
72}
73
74void TextBlock::wrapLine(const KTextEditor::Cursor position, int fixStartLinesStartIndex)
75{
76 // calc internal line
77 const int line = position.line() - startLine();
78
79 // get text, copy, we might invalidate the reference
80 const QString text = m_lines.at(n: line).text();
81
82 // check if valid column
83 Q_ASSERT(position.column() >= 0);
84 Q_ASSERT(position.column() <= text.size());
85 Q_ASSERT(fixStartLinesStartIndex == m_blockIndex);
86
87 // create new line and insert it
88 m_lines.insert(position: m_lines.begin() + line + 1, x: TextLine());
89
90 // cases for modification:
91 // 1. line is wrapped in the middle
92 // 2. if empty line is wrapped, mark new line as modified
93 // 3. line-to-be-wrapped is already modified
94 if (position.column() > 0 || text.size() == 0 || m_lines.at(n: line).markedAsModified()) {
95 m_lines.at(n: line + 1).markAsModified(modified: true);
96 } else if (m_lines.at(n: line).markedAsSavedOnDisk()) {
97 m_lines.at(n: line + 1).markAsSavedOnDisk(savedOnDisk: true);
98 }
99
100 // perhaps remove some text from previous line and append it
101 if (position.column() < text.size()) {
102 // text from old line moved first to new one
103 m_lines.at(n: line + 1).text() = text.right(n: text.size() - position.column());
104
105 // now remove wrapped text from old line
106 m_lines.at(n: line).text().chop(n: text.size() - position.column());
107
108 // mark line as modified
109 m_lines.at(n: line).markAsModified(modified: true);
110 }
111
112 // fix all start lines
113 // we need to do this NOW, else the range update will FAIL!
114 // bug 313759
115 m_buffer->fixStartLines(startBlock: fixStartLinesStartIndex + 1, value: 1);
116
117 // notify the text history
118 m_buffer->history().wrapLine(position);
119
120 // cursor and range handling below
121
122 // no cursors will leave or join this block
123
124 // no cursors in this block, no work to do..
125 if (m_cursors.empty()) {
126 return;
127 }
128
129 // move all cursors on the line which has the text inserted
130 // remember all ranges modified, optimize for the standard case of a few ranges
131 QVarLengthArray<TextRange *, 32> changedRanges;
132 for (TextCursor *cursor : m_cursors) {
133 // skip cursors on lines in front of the wrapped one!
134 if (cursor->lineInBlock() < line) {
135 continue;
136 }
137
138 // either this is simple, line behind the wrapped one
139 if (cursor->lineInBlock() > line) {
140 // patch line of cursor
141 cursor->m_line++;
142 }
143
144 // this is the wrapped line
145 else {
146 // skip cursors with too small column
147 if (cursor->column() <= position.column()) {
148 if (cursor->column() < position.column() || !cursor->m_moveOnInsert) {
149 continue;
150 }
151 }
152
153 // move cursor
154
155 // patch line of cursor
156 cursor->m_line++;
157
158 // patch column
159 cursor->m_column -= position.column();
160 }
161
162 // remember range, if any, avoid double insert
163 auto range = cursor->kateRange();
164 if (range && !range->isValidityCheckRequired()) {
165 range->setValidityCheckRequired();
166 changedRanges.push_back(t: range);
167 }
168 }
169
170 // we might need to invalidate ranges or notify about their changes
171 // checkValidity might trigger delete of the range!
172 for (TextRange *range : std::as_const(t&: changedRanges)) {
173 // ensure that we really invalidate bad ranges!
174 range->checkValidity();
175 }
176}
177
178void TextBlock::unwrapLine(int line, TextBlock *previousBlock, int fixStartLinesStartIndex)
179{
180 // two possiblities: either first line of this block or later line
181 if (line == 0) {
182 // we need previous block with at least one line
183 Q_ASSERT(previousBlock);
184 Q_ASSERT(previousBlock->lines() > 0);
185
186 // move last line of previous block to this one, might result in empty block
187 const TextLine oldFirst = m_lines.at(n: 0);
188 const int lastLineOfPreviousBlock = previousBlock->lines() - 1;
189 m_lines[0] = previousBlock->m_lines.back();
190 previousBlock->m_lines.erase(position: previousBlock->m_lines.begin() + (previousBlock->lines() - 1));
191
192 m_buffer->m_blockSizes[m_blockIndex - 1] -= m_lines[0].length() + 1;
193 m_buffer->m_blockSizes[m_blockIndex] += m_lines[0].length();
194
195 const int oldSizeOfPreviousLine = m_lines[0].text().size();
196 if (oldFirst.length() > 0) {
197 // append text
198 m_lines[0].text().append(s: oldFirst.text());
199
200 // mark line as modified, since text was appended
201 m_lines[0].markAsModified(modified: true);
202 }
203
204 // fix all start lines
205 // we need to do this NOW, else the range update will FAIL!
206 // bug 313759
207 Q_ASSERT(fixStartLinesStartIndex + 1 == m_blockIndex);
208 m_buffer->fixStartLines(startBlock: fixStartLinesStartIndex + 1, value: -1);
209
210 // notify the text history in advance
211 m_buffer->history().unwrapLine(line: startLine() + line, oldLineLength: oldSizeOfPreviousLine);
212
213 // cursor and range handling below
214
215 // no cursors in this block and the previous one, no work to do..
216 if (m_cursors.empty() && previousBlock->m_cursors.empty()) {
217 return;
218 }
219
220 // move all cursors because of the unwrapped line
221 // remember all ranges modified, optimize for the standard case of a few ranges
222 QVarLengthArray<QPair<TextRange *, bool>, 32> changedRanges;
223 for (TextCursor *cursor : m_cursors) {
224 // this is the unwrapped line
225 if (cursor->lineInBlock() == 0) {
226 // patch column
227 cursor->m_column += oldSizeOfPreviousLine;
228
229 // remember range, if any, avoid double insert
230 auto range = cursor->kateRange();
231 if (range && !range->isValidityCheckRequired()) {
232 range->setValidityCheckRequired();
233 changedRanges.push_back(t: {range, range->spansMultipleBlocks()});
234 }
235 }
236 }
237
238 // move cursors of the moved line from previous block to this block now
239 for (auto it = previousBlock->m_cursors.begin(); it != previousBlock->m_cursors.end();) {
240 auto cursor = *it;
241 if (cursor->lineInBlock() == lastLineOfPreviousBlock) {
242 Kate::TextRange *range = cursor->kateRange();
243 // get the value before changing the block
244 const bool spansMultipleBlocks = range && range->spansMultipleBlocks();
245 cursor->m_line = 0;
246 cursor->m_block = this;
247 insertCursor(cursor);
248
249 // remember range, if any, avoid double insert
250 if (range && !range->isValidityCheckRequired()) {
251 range->setValidityCheckRequired();
252 // the range might not span multiple blocks anymore
253 changedRanges.push_back(t: {range, spansMultipleBlocks});
254 }
255
256 // remove from previous block
257 it = previousBlock->m_cursors.erase(position: it);
258 } else {
259 // keep in previous block
260 ++it;
261 }
262 }
263
264 // fixup the ranges that might be effected, because they moved from last line to this block
265 // we might need to invalidate ranges or notify about their changes
266 // checkValidity might trigger delete of the range!
267 for (auto [range, wasMultiblock] : changedRanges) {
268 // if the range doesn't span multiple blocks anymore remove it from buffer multiline range cache
269 if (!range->spansMultipleBlocks() && wasMultiblock) {
270 m_buffer->removeMultilineRange(range);
271 }
272 // afterwards check validity, might delete this range!
273 range->checkValidity();
274 }
275
276 // be done
277 return;
278 }
279
280 m_buffer->m_blockSizes[m_blockIndex] -= 1;
281
282 // easy: just move text to previous line and remove current one
283 const int oldSizeOfPreviousLine = m_lines.at(n: line - 1).length();
284 const int sizeOfCurrentLine = m_lines.at(n: line).length();
285 if (sizeOfCurrentLine > 0) {
286 m_lines.at(n: line - 1).text().append(s: m_lines.at(n: line).text());
287 }
288
289 const bool lineChanged = (oldSizeOfPreviousLine > 0 && m_lines.at(n: line - 1).markedAsModified())
290 || (sizeOfCurrentLine > 0 && (oldSizeOfPreviousLine > 0 || m_lines.at(n: line).markedAsModified()));
291 m_lines.at(n: line - 1).markAsModified(modified: lineChanged);
292 if (oldSizeOfPreviousLine == 0 && m_lines.at(n: line).markedAsSavedOnDisk()) {
293 m_lines.at(n: line - 1).markAsSavedOnDisk(savedOnDisk: true);
294 }
295
296 m_lines.erase(position: m_lines.begin() + line);
297
298 // fix all start lines
299 // we need to do this NOW, else the range update will FAIL!
300 // bug 313759
301 m_buffer->fixStartLines(startBlock: fixStartLinesStartIndex + 1, value: -1);
302
303 // notify the text history in advance
304 m_buffer->history().unwrapLine(line: startLine() + line, oldLineLength: oldSizeOfPreviousLine);
305
306 // cursor and range handling below
307
308 // no cursors in this block, no work to do..
309 if (m_cursors.empty()) {
310 return;
311 }
312
313 // move all cursors because of the unwrapped line
314 // remember all ranges modified, optimize for the standard case of a few ranges
315 QVarLengthArray<TextRange *, 32> changedRanges;
316 for (TextCursor *cursor : m_cursors) {
317 // skip cursors in lines in front of removed one
318 if (cursor->lineInBlock() < line) {
319 continue;
320 }
321
322 // this is the unwrapped line
323 if (cursor->lineInBlock() == line) {
324 // patch column
325 cursor->m_column += oldSizeOfPreviousLine;
326 }
327
328 // patch line of cursor
329 cursor->m_line--;
330
331 // remember range, if any, avoid double insert
332 auto range = cursor->kateRange();
333 if (range && !range->isValidityCheckRequired()) {
334 range->setValidityCheckRequired();
335 changedRanges.push_back(t: range);
336 }
337 }
338
339 // we might need to invalidate ranges or notify about their changes
340 // checkValidity might trigger delete of the range!
341 for (TextRange *range : std::as_const(t&: changedRanges)) {
342 // ensure that we really invalidate bad ranges!
343 range->checkValidity();
344 }
345}
346
347void TextBlock::insertText(const KTextEditor::Cursor position, const QString &text)
348{
349 // calc internal line
350 int line = position.line() - startLine();
351
352 // get text
353 QString &textOfLine = m_lines.at(n: line).text();
354 int oldLength = textOfLine.size();
355 m_lines.at(n: line).markAsModified(modified: true);
356
357 // check if valid column
358 Q_ASSERT(position.column() >= 0);
359 Q_ASSERT(position.column() <= textOfLine.size());
360
361 // insert text
362 textOfLine.insert(i: position.column(), s: text);
363
364 // notify the text history
365 m_buffer->history().insertText(position, length: text.size(), oldLineLength: oldLength);
366
367 // cursor and range handling below
368
369 // no cursors in this block, no work to do..
370 if (m_cursors.empty()) {
371 return;
372 }
373
374 // move all cursors on the line which has the text inserted
375 // remember all ranges modified, optimize for the standard case of a few ranges
376 QVarLengthArray<TextRange *, 32> changedRanges;
377 for (TextCursor *cursor : m_cursors) {
378 // skip cursors not on this line!
379 if (cursor->lineInBlock() != line) {
380 continue;
381 }
382
383 // skip cursors with too small column
384 if (cursor->column() <= position.column()) {
385 if (cursor->column() < position.column() || !cursor->m_moveOnInsert) {
386 continue;
387 }
388 }
389
390 // patch column of cursor
391 if (cursor->m_column <= oldLength) {
392 cursor->m_column += text.size();
393 }
394
395 // special handling if cursor behind the real line, e.g. non-wrapping cursor in block selection mode
396 else if (cursor->m_column < textOfLine.size()) {
397 cursor->m_column = textOfLine.size();
398 }
399
400 // remember range, if any, avoid double insert
401 // we only need to trigger checkValidity later if the range has feedback or might be invalidated
402 auto range = cursor->kateRange();
403 if (range && !range->isValidityCheckRequired() && (range->feedback() || range->start().line() == range->end().line())) {
404 range->setValidityCheckRequired();
405 changedRanges.push_back(t: range);
406 }
407 }
408
409 // we might need to invalidate ranges or notify about their changes
410 // checkValidity might trigger delete of the range!
411 for (TextRange *range : std::as_const(t&: changedRanges)) {
412 range->checkValidity();
413 }
414}
415
416void TextBlock::removeText(KTextEditor::Range range, QString &removedText)
417{
418 // calc internal line
419 int line = range.start().line() - startLine();
420
421 // get text
422 QString &textOfLine = m_lines.at(n: line).text();
423 int oldLength = textOfLine.size();
424
425 // check if valid column
426 Q_ASSERT(range.start().column() >= 0);
427 Q_ASSERT(range.start().column() <= textOfLine.size());
428 Q_ASSERT(range.end().column() >= 0);
429 Q_ASSERT(range.end().column() <= textOfLine.size());
430
431 // get text which will be removed
432 removedText = textOfLine.mid(position: range.start().column(), n: range.end().column() - range.start().column());
433
434 // remove text
435 textOfLine.remove(i: range.start().column(), len: range.end().column() - range.start().column());
436 m_lines.at(n: line).markAsModified(modified: true);
437
438 // notify the text history
439 m_buffer->history().removeText(range, oldLineLength: oldLength);
440
441 // cursor and range handling below
442
443 // no cursors in this block, no work to do..
444 if (m_cursors.empty()) {
445 return;
446 }
447
448 // move all cursors on the line which has the text removed
449 // remember all ranges modified, optimize for the standard case of a few ranges
450 QVarLengthArray<TextRange *, 32> changedRanges;
451 for (TextCursor *cursor : m_cursors) {
452 // skip cursors not on this line!
453 if (cursor->lineInBlock() != line) {
454 continue;
455 }
456
457 // skip cursors with too small column
458 if (cursor->column() <= range.start().column()) {
459 continue;
460 }
461
462 // patch column of cursor
463 if (cursor->column() <= range.end().column()) {
464 cursor->m_column = range.start().column();
465 } else {
466 cursor->m_column -= (range.end().column() - range.start().column());
467 }
468
469 // remember range, if any, avoid double insert
470 // we only need to trigger checkValidity later if the range has feedback or might be invalidated
471 auto range = cursor->kateRange();
472 if (range && !range->isValidityCheckRequired() && (range->feedback() || range->start().line() == range->end().line())) {
473 range->setValidityCheckRequired();
474 changedRanges.push_back(t: range);
475 }
476 }
477
478 // we might need to invalidate ranges or notify about their changes
479 // checkValidity might trigger delete of the range!
480 for (TextRange *range : std::as_const(t&: changedRanges)) {
481 range->checkValidity();
482 }
483}
484
485void TextBlock::debugPrint(int blockIndex) const
486{
487 // print all blocks
488 for (size_t i = 0; i < m_lines.size(); ++i) {
489 printf(format: "%4d - %4llu : %4llu : '%s'\n",
490 blockIndex,
491 (unsigned long long)startLine() + i,
492 (unsigned long long)m_lines.at(n: i).text().size(),
493 qPrintable(m_lines.at(i).text()));
494 }
495}
496
497void TextBlock::splitBlock(int fromLine, TextBlock *newBlock)
498{
499 Q_ASSERT(newBlock->m_cursors.empty());
500 // move lines
501 auto myLinesToMoveBegin = m_lines.begin() + fromLine;
502 auto myLinesToMoveEnd = m_lines.end();
503 int blockSizeChange = myLinesToMoveEnd - myLinesToMoveBegin;// how many newlines
504 std::for_each(first: myLinesToMoveBegin, last: myLinesToMoveEnd, f: [&blockSizeChange] (const TextLine &line) -> void {
505 blockSizeChange += line.length();// how many non-newlines
506 });
507 m_buffer->m_blockSizes[m_blockIndex] -= blockSizeChange;
508 m_buffer->m_blockSizes[newBlock->m_blockIndex] += blockSizeChange;
509 newBlock->m_lines.insert(position: newBlock->m_lines.cend(), first: std::make_move_iterator(i: myLinesToMoveBegin), last: std::make_move_iterator(i: myLinesToMoveEnd));
510 m_lines.resize(new_size: fromLine);
511
512 // move cursors
513 QSet<Kate::TextRange *> ranges;
514 for (auto it = m_cursors.begin(); it != m_cursors.end();) {
515 auto cursor = *it;
516 if (cursor->lineInBlock() >= fromLine) {
517 cursor->m_line = cursor->lineInBlock() - fromLine;
518 cursor->m_block = newBlock;
519
520 // add to new, remove from current
521 newBlock->m_cursors.push_back(x: cursor);
522 it = m_cursors.erase(position: it);
523 if (cursor->kateRange()) {
524 ranges.insert(value: cursor->kateRange());
525 }
526 } else {
527 // keep in current
528 ++it;
529 }
530 }
531 Q_ASSERT(std::is_sorted(newBlock->m_cursors.cbegin(), newBlock->m_cursors.cend()));
532
533 for (auto range : std::as_const(t&: ranges)) {
534 if (range->spansMultipleBlocks()) {
535 m_buffer->addMultilineRange(range);
536 }
537 }
538}
539
540void TextBlock::mergeBlock(TextBlock *targetBlock)
541{
542 // This function moves everything from *this into *targetBlock.
543 // *targetBlock exists before *this with no blocks between.
544 // Both this->m_cursors and targetBlock->m_cursors are sorted.
545
546 // Iterating m_cursors backwards to modify TextRange's m_end before m_start.
547 std::for_each(first: m_cursors.crbegin(), last: m_cursors.crend(), f: [targetBlockLines = targetBlock->lines(), targetBlock, m_buffer = m_buffer] (TextCursor *cursor) -> void {
548 cursor->m_line += targetBlockLines;
549 cursor->m_block = targetBlock;
550 TextRange *range = cursor->m_range;
551 if (range && cursor == &range->m_end && targetBlock == range->m_start.m_block) {
552 m_buffer->removeMultilineRange(range);
553 }
554 });
555 // move cursors
556 auto first_insertion_pos = targetBlock->m_cursors.insert(position: targetBlock->m_cursors.cend(), first: m_cursors.cbegin(), last: m_cursors.cend());
557 m_cursors.clear();
558 // keep targetBlock->m_cursors sorted
559 std::inplace_merge(first: targetBlock->m_cursors.begin(), middle: first_insertion_pos, last: targetBlock->m_cursors.end());
560 Q_ASSERT(std::is_sorted(targetBlock->m_cursors.cbegin(), targetBlock->m_cursors.cend()));
561 // move lines
562 targetBlock->m_lines.insert(position: targetBlock->m_lines.cend(), first: std::make_move_iterator(i: m_lines.begin()), last: std::make_move_iterator(i: m_lines.end()));
563 m_lines.clear();
564}
565
566void TextBlock::rangesForLine(const int line, KTextEditor::View *view, bool rangesWithAttributeOnly, QList<TextRange *> &outRanges) const
567{
568 const int lineInBlock = line - startLine(); // line number in block
569 for (TextCursor *cursor : std::as_const(t: m_cursors)) {
570 if (!cursor->kateRange()) {
571 continue;
572 }
573 TextRange *range = cursor->kateRange();
574 if (rangesWithAttributeOnly && !range->hasAttribute()) {
575 continue;
576 }
577
578 // we want ranges for no view, but this one's attribute is only valid for views
579 if (!view && range->attributeOnlyForViews()) {
580 continue;
581 }
582
583 // the range's attribute is not valid for this view
584 if (range->view() && range->view() != view) {
585 continue;
586 }
587
588 if (
589 // simple case
590 (cursor->lineInBlock() == lineInBlock) ||
591 // if line is in the range, ok
592 (range->startInternal().lineInternal() <= line && line <= range->endInternal().lineInternal())
593 ) {
594 outRanges.append(t: range);
595 }
596 }
597}
598
599void TextBlock::markModifiedLinesAsSaved()
600{
601 // mark all modified lines as saved
602 for (auto &textLine : m_lines) {
603 if (textLine.markedAsModified()) {
604 textLine.markAsSavedOnDisk(savedOnDisk: true);
605 }
606 }
607}
608}
609

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