KTextEditor

katetextblock.cpp
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{
15 : m_buffer(buffer)
16 , m_blockIndex(index)
17{
18 // reserve the block size
19 m_lines.reserve(BufferBlockSize);
20}
21
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
32{
33 return m_buffer->m_startLines[m_blockIndex];
34}
35
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(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(line).text();
51 m_lines.at(line) = textLine;
52 m_lines.at(line).text() = originalText;
53}
54
55void TextBlock::appendLine(const QString &textOfLine)
56{
57 m_lines.emplace_back(textOfLine);
58}
59
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(line.text());
70 text.append(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(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(m_lines.begin() + line + 1, 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(line).markedAsModified()) {
95 m_lines.at(line + 1).markAsModified(true);
96 } else if (m_lines.at(line).markedAsSavedOnDisk()) {
97 m_lines.at(line + 1).markAsSavedOnDisk(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(line + 1).text() = text.right(text.size() - position.column());
104
105 // now remove wrapped text from old line
106 m_lines.at(line).text().chop(text.size() - position.column());
107
108 // mark line as modified
109 m_lines.at(line).markAsModified(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(fixStartLinesStartIndex + 1, 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
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(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(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(0);
188 const int lastLineOfPreviousBlock = previousBlock->lines() - 1;
189 m_lines[0] = previousBlock->m_lines.back();
190 previousBlock->m_lines.erase(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(oldFirst.text());
199
200 // mark line as modified, since text was appended
201 m_lines[0].markAsModified(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(fixStartLinesStartIndex + 1, -1);
209
210 // notify the text history in advance
211 m_buffer->history().unwrapLine(startLine() + line, 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
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({range, false});
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({range, spansMultipleBlocks});
254 }
255
256 // remove from previous block
257 it = previousBlock->m_cursors.erase(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(line - 1).length();
284 const int sizeOfCurrentLine = m_lines.at(line).length();
285 if (sizeOfCurrentLine > 0) {
286 m_lines.at(line - 1).text().append(m_lines.at(line).text());
287 }
288
289 const bool lineChanged = (oldSizeOfPreviousLine > 0 && m_lines.at(line - 1).markedAsModified())
290 || (sizeOfCurrentLine > 0 && (oldSizeOfPreviousLine > 0 || m_lines.at(line).markedAsModified()));
291 m_lines.at(line - 1).markAsModified(lineChanged);
292 if (oldSizeOfPreviousLine == 0 && m_lines.at(line).markedAsSavedOnDisk()) {
293 m_lines.at(line - 1).markAsSavedOnDisk(true);
294 }
295
296 m_lines.erase(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(fixStartLinesStartIndex + 1, -1);
302
303 // notify the text history in advance
304 m_buffer->history().unwrapLine(startLine() + line, 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
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(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(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(line).text();
354 int oldLength = textOfLine.size();
355 m_lines.at(line).markAsModified(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(position.column(), text);
363
364 // notify the text history
365 m_buffer->history().insertText(position, text.size(), 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
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(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(changedRanges)) {
412 range->checkValidity();
413 }
414}
415
417{
418 // calc internal line
419 int line = range.start().line() - startLine();
420
421 // get text
422 QString &textOfLine = m_lines.at(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(range.start().column(), range.end().column() - range.start().column());
433
434 // remove text
435 textOfLine.remove(range.start().column(), range.end().column() - range.start().column());
436 m_lines.at(line).markAsModified(true);
437
438 // notify the text history
439 m_buffer->history().removeText(range, 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
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(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(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("%4d - %4llu : %4llu : '%s'\n",
490 blockIndex,
491 (unsigned long long)startLine() + i,
492 (unsigned long long)m_lines.at(i).text().size(),
493 qPrintable(m_lines.at(i).text()));
494 }
495}
496
497void TextBlock::splitBlock(int fromLine, TextBlock *newBlock)
498{
499 // half the block
500 const int linesOfNewBlock = lines() - fromLine;
501
502 // move lines
503 newBlock->m_lines.reserve(linesOfNewBlock);
504 for (size_t i = fromLine; i < m_lines.size(); ++i) {
505 auto line = std::move(m_lines[i]);
506 const int lineLength = line.length();
507 m_buffer->m_blockSizes[m_blockIndex] -= lineLength + 1;
508 m_buffer->m_blockSizes[newBlock->m_blockIndex] += lineLength + 1;
509 newBlock->m_lines.push_back(std::move(line));
510 }
511
512 m_lines.resize(fromLine);
513
514 // move cursors
516 for (auto it = m_cursors.begin(); it != m_cursors.end();) {
517 auto cursor = *it;
518 if (cursor->lineInBlock() >= fromLine) {
519 cursor->m_line = cursor->lineInBlock() - fromLine;
520 cursor->m_block = newBlock;
521
522 // add to new, remove from current
523 newBlock->m_cursors.push_back(cursor);
524 it = m_cursors.erase(it);
525 if (cursor->kateRange()) {
526 ranges.insert(cursor->kateRange());
527 }
528 } else {
529 // keep in current
530 ++it;
531 }
532 }
533 // sort the cursors
534 std::sort(newBlock->m_cursors.begin(), newBlock->m_cursors.end());
535
536 for (auto range : std::as_const(ranges)) {
537 if (range->spansMultipleBlocks()) {
538 m_buffer->addMultilineRange(range);
539 }
540 }
541}
542
544{
545 // move cursors, do this first, now still lines() count is correct for target
547 for (TextCursor *cursor : m_cursors) {
548 cursor->m_line = cursor->lineInBlock() + targetBlock->lines();
549 cursor->m_block = targetBlock;
550 targetBlock->m_cursors.push_back(cursor);
551 if (cursor->kateRange()) {
552 ranges.insert(cursor->kateRange());
553 }
554 }
555 m_cursors.clear();
556 std::sort(targetBlock->m_cursors.begin(), targetBlock->m_cursors.end());
557
558 for (auto range : std::as_const(ranges)) {
559 if (!range->spansMultipleBlocks()) {
560 m_buffer->removeMultilineRange(range);
561 }
562 }
563
564 // move lines
565 targetBlock->m_lines.reserve(targetBlock->lines() + lines());
566 for (size_t i = 0; i < m_lines.size(); ++i) {
567 targetBlock->m_lines.push_back(m_lines.at(i));
568 }
569 clearLines();
570}
571
572QList<TextRange *> TextBlock::rangesForLine(int line, KTextEditor::View *view, bool rangesWithAttributeOnly) const
573{
574 QList<TextRange *> ranges;
575 rangesForLine(line, view, rangesWithAttributeOnly, ranges);
576 return ranges;
577}
578
579void TextBlock::rangesForLine(const int line, KTextEditor::View *view, bool rangesWithAttributeOnly, QList<TextRange *> &outRanges) const
580{
581 outRanges.clear();
582 const int lineInBlock = line - startLine(); // line number in block
583 for (auto cursor : std::as_const(m_cursors)) {
584 if (!cursor->kateRange()) {
585 continue;
586 }
587 auto range = cursor->kateRange();
588 if (rangesWithAttributeOnly && !range->hasAttribute()) {
589 continue;
590 }
591
592 // we want ranges for no view, but this one's attribute is only valid for views
593 if (!view && range->attributeOnlyForViews()) {
594 continue;
595 }
596
597 // the range's attribute is not valid for this view
598 if (range->view() && range->view() != view) {
599 continue;
600 }
601
602 // simple case
603 if (cursor->lineInBlock() == lineInBlock) {
604 outRanges.append(range);
605 }
606 // if line is in the range, ok
607 else if (range->startInternal().lineInternal() <= line && line <= range->endInternal().lineInternal()) {
608 outRanges.append(range);
609 }
610 }
611 std::sort(outRanges.begin(), outRanges.end());
612 auto it = std::unique(outRanges.begin(), outRanges.end());
613 outRanges.erase(it, outRanges.end());
614}
615
617{
618 // mark all modified lines as saved
619 for (auto &textLine : m_lines) {
620 if (textLine.markedAsModified()) {
621 textLine.markAsSavedOnDisk(true);
622 }
623 }
624}
625}
The Cursor represents a position in a Document.
Definition cursor.h:75
constexpr int column() const noexcept
Retrieve the column on which this cursor is situated.
Definition cursor.h:192
constexpr int line() const noexcept
Retrieve the line on which this cursor is situated.
Definition cursor.h:174
An object representing a section of text, from one Cursor to another.
constexpr Cursor end() const noexcept
Get the end position of this range.
constexpr Cursor start() const noexcept
Get the start position of this range.
A text widget with KXMLGUIClient that represents a Document.
Definition view.h:244
Class representing a text block.
KTEXTEDITOR_EXPORT int startLine() const
Start line of this block.
void wrapLine(const KTextEditor::Cursor position, int fixStartLinesStartIndex)
Wrap line at given cursor position.
int lineLength(int line) const
Retrieve length for line.
void insertText(const KTextEditor::Cursor position, const QString &text)
Insert text at given cursor position.
void mergeBlock(TextBlock *targetBlock)
Merge this block with given one, the given one must be a direct predecessor.
void removeText(KTextEditor::Range range, QString &removedText)
Remove text at given range.
KTEXTEDITOR_EXPORT QList< TextRange * > rangesForLine(int line, KTextEditor::View *view, bool rangesWithAttributeOnly) const
Return all ranges in this block which might intersect the given line.
void text(QString &text) const
Retrieve text of block.
void appendLine(const QString &textOfLine)
Append a new line with given text.
void setLineMetaData(int line, const TextLine &textLine)
Transfer all non text attributes for the given line from the given text line to the one in the block.
void clearLines()
Clear the lines.
~TextBlock()
Destruct the text block.
void markModifiedLinesAsSaved()
Flag all modified text lines as saved on disk.
TextLine line(int line) const
Retrieve a text line.
void unwrapLine(int line, TextBlock *previousBlock, int fixStartLinesStartIndex)
Unwrap given line.
void splitBlock(int fromLine, TextBlock *newBlock)
Split given block.
TextBlock(TextBuffer *buffer, int blockIndex)
Construct an empty text block.
int lines() const
Number of lines in this block.
void debugPrint(int blockIndex) const
Debug output, print whole block content with line numbers and line length.
void insertCursor(Kate::TextCursor *cursor)
Insert cursor into this block.
Class representing a text buffer.
TextHistory & history()
TextHistory of this buffer.
KTEXTEDITOR_NO_EXPORT void addMultilineRange(TextRange *range)
Add/Remove a multiline range that spans multiple blocks.
Class representing a 'clever' text cursor.
Class representing a single text line.
const QString & text() const
Accessor to the text contained in this line.
int length() const
Returns the line's length.
Class representing a 'clever' text range.
void append(QList< T > &&value)
iterator begin()
void clear()
iterator end()
iterator erase(const_iterator begin, const_iterator end)
void clear()
iterator insert(const T &value)
QString & insert(qsizetype position, QChar ch)
QString mid(qsizetype position, qsizetype n) const const
QString & remove(QChar ch, Qt::CaseSensitivity cs)
qsizetype size() const const
void push_back(T &&t)
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Nov 22 2024 12:07:21 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.