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_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
33{
34 return m_buffer->m_startLines[m_blockIndex];
35}
36
38{
39 // right input
40 Q_ASSERT(size_t(line) < m_lines.size());
41 // get text line, at will bail out on out-of-range
42 return m_lines.at(line);
43}
44
45void TextBlock::setLineMetaData(int line, const TextLine &textLine)
46{
47 // right input
48 Q_ASSERT(size_t(line) < m_lines.size());
49
50 // set stuff, at will bail out on out-of-range
51 const QString originalText = m_lines.at(line).text();
52 m_lines.at(line) = textLine;
53 m_lines.at(line).text() = originalText;
54}
55
56void TextBlock::appendLine(const QString &textOfLine)
57{
58 m_lines.emplace_back(textOfLine);
59 m_blockSize += textOfLine.size();
60}
61
63{
64 m_lines.clear();
65 m_blockSize = 0;
66}
67
68void TextBlock::text(QString &text) const
69{
70 // combine all lines
71 for (const auto &line : m_lines) {
72 text.append(line.text());
73 text.append(QLatin1Char('\n'));
74 }
75}
76
77void TextBlock::wrapLine(const KTextEditor::Cursor position, int fixStartLinesStartIndex)
78{
79 // calc internal line
80 const int line = position.line() - startLine();
81
82 // get text, copy, we might invalidate the reference
83 const QString text = m_lines.at(line).text();
84
85 // check if valid column
86 Q_ASSERT(position.column() >= 0);
87 Q_ASSERT(position.column() <= text.size());
88 Q_ASSERT(fixStartLinesStartIndex == m_blockIndex);
89
90 // create new line and insert it
91 m_lines.insert(m_lines.begin() + line + 1, TextLine());
92
93 // cases for modification:
94 // 1. line is wrapped in the middle
95 // 2. if empty line is wrapped, mark new line as modified
96 // 3. line-to-be-wrapped is already modified
97 if (position.column() > 0 || text.size() == 0 || m_lines.at(line).markedAsModified()) {
98 m_lines.at(line + 1).markAsModified(true);
99 } else if (m_lines.at(line).markedAsSavedOnDisk()) {
100 m_lines.at(line + 1).markAsSavedOnDisk(true);
101 }
102
103 // perhaps remove some text from previous line and append it
104 if (position.column() < text.size()) {
105 // text from old line moved first to new one
106 m_lines.at(line + 1).text() = text.right(text.size() - position.column());
107
108 // now remove wrapped text from old line
109 m_lines.at(line).text().chop(text.size() - position.column());
110
111 // mark line as modified
112 m_lines.at(line).markAsModified(true);
113 }
114
115 // fix all start lines
116 // we need to do this NOW, else the range update will FAIL!
117 // bug 313759
118 m_buffer->fixStartLines(fixStartLinesStartIndex + 1, 1);
119
120 // notify the text history
121 m_buffer->history().wrapLine(position);
122
123 // cursor and range handling below
124
125 // no cursors will leave or join this block
126
127 // no cursors in this block, no work to do..
128 if (m_cursors.empty()) {
129 return;
130 }
131
132 // move all cursors on the line which has the text inserted
133 // remember all ranges modified, optimize for the standard case of a few ranges
135 for (TextCursor *cursor : m_cursors) {
136 // skip cursors on lines in front of the wrapped one!
137 if (cursor->lineInBlock() < line) {
138 continue;
139 }
140
141 // either this is simple, line behind the wrapped one
142 if (cursor->lineInBlock() > line) {
143 // patch line of cursor
144 cursor->m_line++;
145 }
146
147 // this is the wrapped line
148 else {
149 // skip cursors with too small column
150 if (cursor->column() <= position.column()) {
151 if (cursor->column() < position.column() || !cursor->m_moveOnInsert) {
152 continue;
153 }
154 }
155
156 // move cursor
157
158 // patch line of cursor
159 cursor->m_line++;
160
161 // patch column
162 cursor->m_column -= position.column();
163 }
164
165 // remember range, if any, avoid double insert
166 auto range = cursor->kateRange();
167 if (range && !range->isValidityCheckRequired()) {
168 range->setValidityCheckRequired();
169 changedRanges.push_back(range);
170 }
171 }
172
173 // we might need to invalidate ranges or notify about their changes
174 // checkValidity might trigger delete of the range!
175 for (TextRange *range : std::as_const(changedRanges)) {
176 // ensure that we really invalidate bad ranges!
177 range->checkValidity();
178 }
179}
180
181void TextBlock::unwrapLine(int line, TextBlock *previousBlock, int fixStartLinesStartIndex)
182{
183 // two possiblities: either first line of this block or later line
184 if (line == 0) {
185 // we need previous block with at least one line
186 Q_ASSERT(previousBlock);
187 Q_ASSERT(previousBlock->lines() > 0);
188
189 // move last line of previous block to this one, might result in empty block
190 const TextLine oldFirst = m_lines.at(0);
191 const int lastLineOfPreviousBlock = previousBlock->lines() - 1;
192 m_lines[0] = previousBlock->m_lines.back();
193 previousBlock->m_lines.erase(previousBlock->m_lines.begin() + (previousBlock->lines() - 1));
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);
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 cursor->m_line = 0;
243 cursor->m_block = this;
244 m_cursors.insert(cursor);
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(range);
251 }
252
253 // remove from previous block
254 it = previousBlock->m_cursors.erase(it);
255 } else {
256 // keep in previous block
257 ++it;
258 }
259 }
260
261 // fixup the ranges that might be effected, because they moved from last line to this block
262 // we might need to invalidate ranges or notify about their changes
263 // checkValidity might trigger delete of the range!
264 for (TextRange *range : std::as_const(changedRanges)) {
265 // afterwards check validity, might delete this range!
266 range->checkValidity();
267 }
268
269 // be done
270 return;
271 }
272
273 // easy: just move text to previous line and remove current one
274 const int oldSizeOfPreviousLine = m_lines.at(line - 1).length();
275 const int sizeOfCurrentLine = m_lines.at(line).length();
276 if (sizeOfCurrentLine > 0) {
277 m_lines.at(line - 1).text().append(m_lines.at(line).text());
278 }
279
280 const bool lineChanged = (oldSizeOfPreviousLine > 0 && m_lines.at(line - 1).markedAsModified())
281 || (sizeOfCurrentLine > 0 && (oldSizeOfPreviousLine > 0 || m_lines.at(line).markedAsModified()));
282 m_lines.at(line - 1).markAsModified(lineChanged);
283 if (oldSizeOfPreviousLine == 0 && m_lines.at(line).markedAsSavedOnDisk()) {
284 m_lines.at(line - 1).markAsSavedOnDisk(true);
285 }
286
287 m_lines.erase(m_lines.begin() + line);
288
289 // fix all start lines
290 // we need to do this NOW, else the range update will FAIL!
291 // bug 313759
292 m_buffer->fixStartLines(fixStartLinesStartIndex + 1, -1);
293
294 // notify the text history in advance
295 m_buffer->history().unwrapLine(startLine() + line, oldSizeOfPreviousLine);
296
297 // cursor and range handling below
298
299 // no cursors in this block, no work to do..
300 if (m_cursors.empty()) {
301 return;
302 }
303
304 // move all cursors because of the unwrapped line
305 // remember all ranges modified, optimize for the standard case of a few ranges
307 for (TextCursor *cursor : m_cursors) {
308 // skip cursors in lines in front of removed one
309 if (cursor->lineInBlock() < line) {
310 continue;
311 }
312
313 // this is the unwrapped line
314 if (cursor->lineInBlock() == line) {
315 // patch column
316 cursor->m_column += oldSizeOfPreviousLine;
317 }
318
319 // patch line of cursor
320 cursor->m_line--;
321
322 // remember range, if any, avoid double insert
323 auto range = cursor->kateRange();
324 if (range && !range->isValidityCheckRequired()) {
325 range->setValidityCheckRequired();
326 changedRanges.push_back(range);
327 }
328 }
329
330 // we might need to invalidate ranges or notify about their changes
331 // checkValidity might trigger delete of the range!
332 for (TextRange *range : std::as_const(changedRanges)) {
333 // ensure that we really invalidate bad ranges!
334 range->checkValidity();
335 }
336}
337
338void TextBlock::insertText(const KTextEditor::Cursor position, const QString &text)
339{
340 // calc internal line
341 int line = position.line() - startLine();
342
343 // get text
344 QString &textOfLine = m_lines.at(line).text();
345 int oldLength = textOfLine.size();
346 m_lines.at(line).markAsModified(true);
347
348 // check if valid column
349 Q_ASSERT(position.column() >= 0);
350 Q_ASSERT(position.column() <= textOfLine.size());
351
352 // insert text
353 textOfLine.insert(position.column(), text);
354
355 // notify the text history
356 m_buffer->history().insertText(position, text.size(), oldLength);
357
358 m_blockSize += text.size();
359
360 // cursor and range handling below
361
362 // no cursors in this block, no work to do..
363 if (m_cursors.empty()) {
364 return;
365 }
366
367 // move all cursors on the line which has the text inserted
368 // remember all ranges modified, optimize for the standard case of a few ranges
370 for (TextCursor *cursor : m_cursors) {
371 // skip cursors not on this line!
372 if (cursor->lineInBlock() != line) {
373 continue;
374 }
375
376 // skip cursors with too small column
377 if (cursor->column() <= position.column()) {
378 if (cursor->column() < position.column() || !cursor->m_moveOnInsert) {
379 continue;
380 }
381 }
382
383 // patch column of cursor
384 if (cursor->m_column <= oldLength) {
385 cursor->m_column += text.size();
386 }
387
388 // special handling if cursor behind the real line, e.g. non-wrapping cursor in block selection mode
389 else if (cursor->m_column < textOfLine.size()) {
390 cursor->m_column = textOfLine.size();
391 }
392
393 // remember range, if any, avoid double insert
394 // we only need to trigger checkValidity later if the range has feedback or might be invalidated
395 auto range = cursor->kateRange();
396 if (range && !range->isValidityCheckRequired() && (range->feedback() || range->start().line() == range->end().line())) {
397 range->setValidityCheckRequired();
398 changedRanges.push_back(range);
399 }
400 }
401
402 // we might need to invalidate ranges or notify about their changes
403 // checkValidity might trigger delete of the range!
404 for (TextRange *range : std::as_const(changedRanges)) {
405 range->checkValidity();
406 }
407}
408
410{
411 // calc internal line
412 int line = range.start().line() - startLine();
413
414 // get text
415 QString &textOfLine = m_lines.at(line).text();
416 int oldLength = textOfLine.size();
417
418 // check if valid column
419 Q_ASSERT(range.start().column() >= 0);
420 Q_ASSERT(range.start().column() <= textOfLine.size());
421 Q_ASSERT(range.end().column() >= 0);
422 Q_ASSERT(range.end().column() <= textOfLine.size());
423
424 // get text which will be removed
425 removedText = textOfLine.mid(range.start().column(), range.end().column() - range.start().column());
426
427 // remove text
428 textOfLine.remove(range.start().column(), range.end().column() - range.start().column());
429 m_lines.at(line).markAsModified(true);
430
431 // notify the text history
432 m_buffer->history().removeText(range, oldLength);
433
434 m_blockSize -= removedText.size();
435
436 // cursor and range handling below
437
438 // no cursors in this block, no work to do..
439 if (m_cursors.empty()) {
440 return;
441 }
442
443 // move all cursors on the line which has the text removed
444 // remember all ranges modified, optimize for the standard case of a few ranges
446 for (TextCursor *cursor : m_cursors) {
447 // skip cursors not on this line!
448 if (cursor->lineInBlock() != line) {
449 continue;
450 }
451
452 // skip cursors with too small column
453 if (cursor->column() <= range.start().column()) {
454 continue;
455 }
456
457 // patch column of cursor
458 if (cursor->column() <= range.end().column()) {
459 cursor->m_column = range.start().column();
460 } else {
461 cursor->m_column -= (range.end().column() - range.start().column());
462 }
463
464 // remember range, if any, avoid double insert
465 // we only need to trigger checkValidity later if the range has feedback or might be invalidated
466 auto range = cursor->kateRange();
467 if (range && !range->isValidityCheckRequired() && (range->feedback() || range->start().line() == range->end().line())) {
468 range->setValidityCheckRequired();
469 changedRanges.push_back(range);
470 }
471 }
472
473 // we might need to invalidate ranges or notify about their changes
474 // checkValidity might trigger delete of the range!
475 for (TextRange *range : std::as_const(changedRanges)) {
476 range->checkValidity();
477 }
478}
479
480void TextBlock::debugPrint(int blockIndex) const
481{
482 // print all blocks
483 for (size_t i = 0; i < m_lines.size(); ++i) {
484 printf("%4d - %4llu : %4llu : '%s'\n",
485 blockIndex,
486 (unsigned long long)startLine() + i,
487 (unsigned long long)m_lines.at(i).text().size(),
488 qPrintable(m_lines.at(i).text()));
489 }
490}
491
492void TextBlock::splitBlock(int fromLine, TextBlock *newBlock)
493{
494 // half the block
495 const int linesOfNewBlock = lines() - fromLine;
496
497 // move lines
498 newBlock->m_lines.reserve(linesOfNewBlock);
499 for (size_t i = fromLine; i < m_lines.size(); ++i) {
500 auto line = std::move(m_lines[i]);
501 m_blockSize -= line.length();
502 newBlock->m_blockSize += line.length();
503 newBlock->m_lines.push_back(std::move(line));
504 }
505
506 m_lines.resize(fromLine);
507
508 // move cursors
510 for (auto it = m_cursors.begin(); it != m_cursors.end();) {
511 auto cursor = *it;
512 if (cursor->lineInBlock() >= fromLine) {
513 cursor->m_line = cursor->lineInBlock() - fromLine;
514 cursor->m_block = newBlock;
515
516 // add to new, remove from current
517 newBlock->m_cursors.insert(cursor);
518 it = m_cursors.erase(it);
519 if (cursor->kateRange()) {
520 ranges.insert(cursor->kateRange());
521 }
522 } else {
523 // keep in current
524 ++it;
525 }
526 }
527
528 for (auto range : std::as_const(ranges)) {
529 if (range->spansMultipleBlocks()) {
530 m_buffer->addMultilineRange(range);
531 }
532 }
533}
534
536{
537 // move cursors, do this first, now still lines() count is correct for target
539 for (TextCursor *cursor : m_cursors) {
540 cursor->m_line = cursor->lineInBlock() + targetBlock->lines();
541 cursor->m_block = targetBlock;
542 targetBlock->m_cursors.insert(cursor);
543 if (cursor->kateRange()) {
544 ranges.insert(cursor->kateRange());
545 }
546 }
547 m_cursors.clear();
548
549 for (auto range : std::as_const(ranges)) {
550 if (!range->spansMultipleBlocks()) {
551 m_buffer->removeMultilineRange(range);
552 }
553 }
554
555 // move lines
556 targetBlock->m_lines.reserve(targetBlock->lines() + lines());
557 for (size_t i = 0; i < m_lines.size(); ++i) {
558 targetBlock->m_lines.push_back(m_lines.at(i));
559 }
560 targetBlock->m_blockSize += m_blockSize;
561 clearLines();
562}
563
565{
566 // kill cursors, if not belonging to a range
567 // we can do in-place editing of the current set of cursors as
568 // we remove them before deleting
569 for (auto it = m_cursors.begin(); it != m_cursors.end();) {
570 auto cursor = *it;
571 if (!cursor->kateRange()) {
572 // remove it and advance to next element
573 it = m_cursors.erase(it);
574
575 // delete after cursor is gone from the set
576 // else the destructor will modify it!
577 delete cursor;
578 } else {
579 // keep this cursor
580 ++it;
581 }
582 }
583
584 // kill lines
585 clearLines();
586}
587
589{
590 // move cursors, if not belonging to a range
591 // we can do in-place editing of the current set of cursors
592 for (auto it = m_cursors.begin(); it != m_cursors.end();) {
593 auto cursor = *it;
594 if (!cursor->kateRange()) {
595 cursor->m_column = 0;
596 cursor->m_line = 0;
597 cursor->m_block = targetBlock;
598 targetBlock->m_cursors.insert(cursor);
599
600 // remove it and advance to next element
601 it = m_cursors.erase(it);
602 } else {
603 // keep this cursor
604 ++it;
605 }
606 }
607
608 // kill lines
609 clearLines();
610}
611
612QList<TextRange *> TextBlock::rangesForLine(int line, KTextEditor::View *view, bool rangesWithAttributeOnly) const
613{
614 QList<TextRange *> ranges;
615 rangesForLine(line, view, rangesWithAttributeOnly, ranges);
616 return ranges;
617}
618
619void TextBlock::rangesForLine(const int line, KTextEditor::View *view, bool rangesWithAttributeOnly, QList<TextRange *> &outRanges) const
620{
621 outRanges.clear();
622 const int lineInBlock = line - startLine(); // line number in block
623 for (auto cursor : std::as_const(m_cursors)) {
624 if (!cursor->kateRange()) {
625 continue;
626 }
627 auto range = cursor->kateRange();
628 if (rangesWithAttributeOnly && !range->hasAttribute()) {
629 continue;
630 }
631
632 // we want ranges for no view, but this one's attribute is only valid for views
633 if (!view && range->attributeOnlyForViews()) {
634 continue;
635 }
636
637 // the range's attribute is not valid for this view
638 if (range->view() && range->view() != view) {
639 continue;
640 }
641
642 // simple case
643 if (cursor->lineInBlock() == lineInBlock) {
644 outRanges.append(range);
645 }
646 // if line is in the range, ok
647 else if (range->startInternal().lineInternal() <= line && line <= range->endInternal().lineInternal()) {
648 outRanges.append(range);
649 }
650 }
651 std::sort(outRanges.begin(), outRanges.end());
652 auto it = std::unique(outRanges.begin(), outRanges.end());
653 outRanges.erase(it, outRanges.end());
654}
655
657{
658 // mark all modified lines as saved
659 for (auto &textLine : m_lines) {
660 if (textLine.markedAsModified()) {
661 textLine.markAsSavedOnDisk(true);
662 }
663 }
664}
665}
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.
void clearBlockContent(TextBlock *targetBlock)
Clear the block content, delete all lines, move all cursors not bound to range to given block at 0,...
KTEXTEDITOR_EXPORT int startLine() const
Start line of this block.
void wrapLine(const KTextEditor::Cursor position, int fixStartLinesStartIndex)
Wrap line at given cursor position.
void deleteBlockContent()
Delete the block content, delete all lines and delete all cursors not bound to ranges.
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.
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 Oct 4 2024 12:03:01 by doxygen 1.12.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.