KTextEditor

katetextblock.cpp
1 /* SPDX-License-Identifier: LGPL-2.0-or-later
2 
3  Copyright (C) 2010 Christoph Cullmann <[email protected]>
4 
5  This library is free software; you can redistribute it and/or
6  modify it under the terms of the GNU Library General Public
7  License as published by the Free Software Foundation; either
8  version 2 of the License, or (at your option) any later version.
9 
10  This library is distributed in the hope that it will be useful,
11  but WITHOUT ANY WARRANTY; without even the implied warranty of
12  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
13  Library General Public License for more details.
14 
15  You should have received a copy of the GNU Library General Public License
16  along with this library; see the file COPYING.LIB. If not, write to
17  the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
18  Boston, MA 02110-1301, USA.
19 */
20 
21 #include "katetextblock.h"
22 #include "katetextbuffer.h"
23 
24 #include <QVarLengthArray>
25 
26 namespace Kate
27 {
28 TextBlock::TextBlock(TextBuffer *buffer, int startLine)
29  : m_buffer(buffer)
30  , m_startLine(startLine)
31 {
32  // reserve the block size
33  m_lines.reserve(m_buffer->m_blockSize);
34 }
35 
37 {
38  // blocks should be empty before they are deleted!
39  Q_ASSERT(m_lines.empty());
40  Q_ASSERT(m_cursors.empty());
41 
42  // it only is a hint for ranges for this block, not the storage of them
43 }
44 
46 {
47  // allow only valid lines
48  Q_ASSERT(startLine >= 0);
49  Q_ASSERT(startLine < m_buffer->lines());
50 
51  m_startLine = startLine;
52 }
53 
55 {
56  // right input
57  Q_ASSERT(line >= startLine());
58 
59  // get text line, at will bail out on out-of-range
60  return m_lines.at(line - startLine());
61 }
62 
63 void TextBlock::appendLine(const QString &textOfLine)
64 {
65  m_lines.push_back(TextLine::create(textOfLine));
66 }
67 
69 {
70  m_lines.clear();
71 }
72 
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(QLatin1Char('\n'));
80  }
81 
82  text.append(m_lines.at(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
92  QString &text = m_lines.at(line)->textReadWrite();
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(m_lines.begin() + line + 1, TextLine(new TextLineData()));
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(line)->markedAsModified()) {
106  m_lines.at(line + 1)->markAsModified(true);
107  } else if (m_lines.at(line)->markedAsSavedOnDisk()) {
108  m_lines.at(line + 1)->markAsSavedOnDisk(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(line + 1)->textReadWrite() = text.right(text.size() - position.column());
115 
116  // now remove wrapped text from old line
117  text.chop(text.size() - position.column());
118 
119  // mark line as modified
120  m_lines.at(line)->markAsModified(true);
121  }
122 
128  m_buffer->fixStartLines(fixStartLinesStartIndex);
129 
133  m_buffer->history().wrapLine(position);
134 
139  // no cursors will leave or join this block
140 
141  // no cursors in this block, no work to do..
142  if (m_cursors.empty()) {
143  return;
144  }
145 
146  // move all cursors on the line which has the text inserted
147  // remember all ranges modified, optimize for the standard case of a few ranges
148  QVarLengthArray<TextRange *, 32> changedRanges;
149  for (TextCursor *cursor : m_cursors) {
150  // skip cursors on lines in front of the wrapped one!
151  if (cursor->lineInBlock() < line) {
152  continue;
153  }
154 
155  // either this is simple, line behind the wrapped one
156  if (cursor->lineInBlock() > line) {
157  // patch line of cursor
158  cursor->m_line++;
159  }
160 
161  // this is the wrapped line
162  else {
163  // skip cursors with too small column
164  if (cursor->column() <= position.column()) {
165  if (cursor->column() < position.column() || !cursor->m_moveOnInsert) {
166  continue;
167  }
168  }
169 
170  // move cursor
171 
172  // patch line of cursor
173  cursor->m_line++;
174 
175  // patch column
176  cursor->m_column -= position.column();
177  }
178 
179  // remember range, if any, avoid double insert
180  auto range = cursor->kateRange();
181  if (range && !range->isValidityCheckRequired()) {
182  range->setValidityCheckRequired();
183  changedRanges.push_back(range);
184  }
185  }
186 
187  // we might need to invalidate ranges or notify about their changes
188  // checkValidity might trigger delete of the range!
189  for (TextRange *range : qAsConst(changedRanges)) {
190  range->checkValidity();
191  }
192 }
193 
194 void TextBlock::unwrapLine(int line, TextBlock *previousBlock, int fixStartLinesStartIndex)
195 {
196  // calc internal line
197  line = line - startLine();
198 
199  // two possiblities: either first line of this block or later line
200  if (line == 0) {
201  // we need previous block with at least one line
202  Q_ASSERT(previousBlock);
203  Q_ASSERT(previousBlock->lines() > 0);
204 
205  // move last line of previous block to this one, might result in empty block
206  TextLine oldFirst = m_lines.at(0);
207  int lastLineOfPreviousBlock = previousBlock->lines() - 1;
208  TextLine newFirst = previousBlock->m_lines.back();
209  m_lines[0] = newFirst;
210  previousBlock->m_lines.erase(previousBlock->m_lines.begin() + (previousBlock->lines() - 1));
211 
212  const int oldSizeOfPreviousLine = newFirst->text().size();
213  if (oldFirst->length() > 0) {
214  // append text
215  newFirst->textReadWrite().append(oldFirst->text());
216 
217  // mark line as modified, since text was appended
218  newFirst->markAsModified(true);
219  }
220 
221  // patch startLine of this block
222  --m_startLine;
223 
229  m_buffer->fixStartLines(fixStartLinesStartIndex);
230 
234  m_buffer->history().unwrapLine(startLine() + line, oldSizeOfPreviousLine);
235 
240  // no cursors in this block and the previous one, no work to do..
241  if (m_cursors.empty() && previousBlock->m_cursors.empty()) {
242  return;
243  }
244 
245  // move all cursors because of the unwrapped line
246  // remember all ranges modified, optimize for the standard case of a few ranges
247  QVarLengthArray<TextRange *, 32> changedRanges;
248  for (TextCursor *cursor : m_cursors) {
249  // this is the unwrapped line
250  if (cursor->lineInBlock() == 0) {
251  // patch column
252  cursor->m_column += oldSizeOfPreviousLine;
253 
254  // remember range, if any, avoid double insert
255  auto range = cursor->kateRange();
256  if (range && !range->isValidityCheckRequired()) {
257  range->setValidityCheckRequired();
258  changedRanges.push_back(range);
259  }
260  }
261  }
262 
263  // move cursors of the moved line from previous block to this block now
264  for (auto it = previousBlock->m_cursors.begin(); it != previousBlock->m_cursors.end();) {
265  auto cursor = *it;
266  if (cursor->lineInBlock() == lastLineOfPreviousBlock) {
267  cursor->m_line = 0;
268  cursor->m_block = this;
269  m_cursors.insert(cursor);
270 
271  // remember range, if any, avoid double insert
272  auto range = cursor->kateRange();
273  if (range && !range->isValidityCheckRequired()) {
274  range->setValidityCheckRequired();
275  changedRanges.push_back(range);
276  }
277 
278  // remove from previous block
279  it = previousBlock->m_cursors.erase(it);
280  } else {
281  // keep in previous block
282  ++it;
283  }
284  }
285 
286  // fixup the ranges that might be effected, because they moved from last line to this block
287  // we might need to invalidate ranges or notify about their changes
288  // checkValidity might trigger delete of the range!
289  for (TextRange *range : qAsConst(changedRanges)) {
290  // update both blocks
291  updateRange(range);
292  previousBlock->updateRange(range);
293 
294  // afterwards check validity, might delete this range!
295  range->checkValidity();
296  }
297 
298  // be done
299  return;
300  }
301 
302  // easy: just move text to previous line and remove current one
303  const int oldSizeOfPreviousLine = m_lines.at(line - 1)->length();
304  const int sizeOfCurrentLine = m_lines.at(line)->length();
305  if (sizeOfCurrentLine > 0) {
306  m_lines.at(line - 1)->textReadWrite().append(m_lines.at(line)->text());
307  }
308 
309  const bool lineChanged = (oldSizeOfPreviousLine > 0 && m_lines.at(line - 1)->markedAsModified()) || (sizeOfCurrentLine > 0 && (oldSizeOfPreviousLine > 0 || m_lines.at(line)->markedAsModified()));
310  m_lines.at(line - 1)->markAsModified(lineChanged);
311  if (oldSizeOfPreviousLine == 0 && m_lines.at(line)->markedAsSavedOnDisk()) {
312  m_lines.at(line - 1)->markAsSavedOnDisk(true);
313  }
314 
315  m_lines.erase(m_lines.begin() + line);
316 
322  m_buffer->fixStartLines(fixStartLinesStartIndex);
323 
327  m_buffer->history().unwrapLine(startLine() + line, oldSizeOfPreviousLine);
328 
333  // no cursors in this block, no work to do..
334  if (m_cursors.empty()) {
335  return;
336  }
337 
338  // move all cursors because of the unwrapped line
339  // remember all ranges modified, optimize for the standard case of a few ranges
340  QVarLengthArray<TextRange *, 32> changedRanges;
341  for (TextCursor *cursor : m_cursors) {
342  // skip cursors in lines in front of removed one
343  if (cursor->lineInBlock() < line) {
344  continue;
345  }
346 
347  // this is the unwrapped line
348  if (cursor->lineInBlock() == line) {
349  // patch column
350  cursor->m_column += oldSizeOfPreviousLine;
351  }
352 
353  // patch line of cursor
354  cursor->m_line--;
355 
356  // remember range, if any, avoid double insert
357  auto range = cursor->kateRange();
358  if (range && !range->isValidityCheckRequired()) {
359  range->setValidityCheckRequired();
360  changedRanges.push_back(range);
361  }
362  }
363 
364  // we might need to invalidate ranges or notify about their changes
365  // checkValidity might trigger delete of the range!
366  for (TextRange *range : qAsConst(changedRanges)) {
367  range->checkValidity();
368  }
369 }
370 
372 {
373  // calc internal line
374  int line = position.line() - startLine();
375 
376  // get text
377  QString &textOfLine = m_lines.at(line)->textReadWrite();
378  int oldLength = textOfLine.size();
379  m_lines.at(line)->markAsModified(true);
380 
381  // check if valid column
382  Q_ASSERT(position.column() >= 0);
383  Q_ASSERT(position.column() <= textOfLine.size());
384 
385  // insert text
386  textOfLine.insert(position.column(), text);
387 
391  m_buffer->history().insertText(position, text.size(), oldLength);
392 
397  // no cursors in this block, no work to do..
398  if (m_cursors.empty()) {
399  return;
400  }
401 
402  // move all cursors on the line which has the text inserted
403  // remember all ranges modified, optimize for the standard case of a few ranges
404  QVarLengthArray<TextRange *, 32> changedRanges;
405  for (TextCursor *cursor : m_cursors) {
406  // skip cursors not on this line!
407  if (cursor->lineInBlock() != line) {
408  continue;
409  }
410 
411  // skip cursors with too small column
412  if (cursor->column() <= position.column()) {
413  if (cursor->column() < position.column() || !cursor->m_moveOnInsert) {
414  continue;
415  }
416  }
417 
418  // patch column of cursor
419  if (cursor->m_column <= oldLength) {
420  cursor->m_column += text.size();
421  }
422 
423  // special handling if cursor behind the real line, e.g. non-wrapping cursor in block selection mode
424  else if (cursor->m_column < textOfLine.size()) {
425  cursor->m_column = textOfLine.size();
426  }
427 
428  // remember range, if any, avoid double insert
429  // we only need to trigger checkValidity later if the range has feedback or might be invalidated
430  auto range = cursor->kateRange();
431  if (range && !range->isValidityCheckRequired() && (range->feedback() || range->start().line() == range->end().line())) {
432  range->setValidityCheckRequired();
433  changedRanges.push_back(range);
434  }
435  }
436 
437  // we might need to invalidate ranges or notify about their changes
438  // checkValidity might trigger delete of the range!
439  for (TextRange *range : qAsConst(changedRanges)) {
440  range->checkValidity();
441  }
442 }
443 
444 void TextBlock::removeText(const KTextEditor::Range &range, QString &removedText)
445 {
446  // calc internal line
447  int line = range.start().line() - startLine();
448 
449  // get text
450  QString &textOfLine = m_lines.at(line)->textReadWrite();
451  int oldLength = textOfLine.size();
452 
453  // check if valid column
454  Q_ASSERT(range.start().column() >= 0);
455  Q_ASSERT(range.start().column() <= textOfLine.size());
456  Q_ASSERT(range.end().column() >= 0);
457  Q_ASSERT(range.end().column() <= textOfLine.size());
458 
459  // get text which will be removed
460  removedText = textOfLine.mid(range.start().column(), range.end().column() - range.start().column());
461 
462  // remove text
463  textOfLine.remove(range.start().column(), range.end().column() - range.start().column());
464  m_lines.at(line)->markAsModified(true);
465 
469  m_buffer->history().removeText(range, oldLength);
470 
475  // no cursors in this block, no work to do..
476  if (m_cursors.empty()) {
477  return;
478  }
479 
480  // move all cursors on the line which has the text removed
481  // remember all ranges modified, optimize for the standard case of a few ranges
482  QVarLengthArray<TextRange *, 32> changedRanges;
483  for (TextCursor *cursor : m_cursors) {
484  // skip cursors not on this line!
485  if (cursor->lineInBlock() != line) {
486  continue;
487  }
488 
489  // skip cursors with too small column
490  if (cursor->column() <= range.start().column()) {
491  continue;
492  }
493 
494  // patch column of cursor
495  if (cursor->column() <= range.end().column()) {
496  cursor->m_column = range.start().column();
497  } else {
498  cursor->m_column -= (range.end().column() - range.start().column());
499  }
500 
501  // remember range, if any, avoid double insert
502  // we only need to trigger checkValidity later if the range has feedback or might be invalidated
503  auto range = cursor->kateRange();
504  if (range && !range->isValidityCheckRequired() && (range->feedback() || range->start().line() == range->end().line())) {
505  range->setValidityCheckRequired();
506  changedRanges.push_back(range);
507  }
508  }
509 
510  // we might need to invalidate ranges or notify about their changes
511  // checkValidity might trigger delete of the range!
512  for (TextRange *range : qAsConst(changedRanges)) {
513  range->checkValidity();
514  }
515 }
516 
517 void TextBlock::debugPrint(int blockIndex) const
518 {
519  // print all blocks
520  for (size_t i = 0; i < m_lines.size(); ++i)
521  printf("%4d - %4lld : %4d : '%s'\n", blockIndex, (unsigned long long)startLine() + i, m_lines.at(i)->text().size(), qPrintable(m_lines.at(i)->text()));
522 }
523 
525 {
526  // half the block
527  int linesOfNewBlock = lines() - fromLine;
528 
529  // create and insert new block
530  TextBlock *newBlock = new TextBlock(m_buffer, startLine() + fromLine);
531 
532  // move lines
533  newBlock->m_lines.reserve(linesOfNewBlock);
534  for (size_t i = fromLine; i < m_lines.size(); ++i) {
535  newBlock->m_lines.push_back(m_lines.at(i));
536  }
537  m_lines.resize(fromLine);
538 
539  // move cursors
540  for (auto it = m_cursors.begin(); it != m_cursors.end();) {
541  auto cursor = *it;
542  if (cursor->lineInBlock() >= fromLine) {
543  cursor->m_line = cursor->lineInBlock() - fromLine;
544  cursor->m_block = newBlock;
545 
546  // add to new, remove from current
547  newBlock->m_cursors.insert(cursor);
548  it = m_cursors.erase(it);
549  } else {
550  // keep in current
551  ++it;
552  }
553  }
554 
555  // fix ALL ranges!
556  const QList<TextRange *> allRanges = m_uncachedRanges.values() + m_cachedLineForRanges.keys();
557  for (TextRange *range : qAsConst(allRanges)) {
558  // update both blocks
559  updateRange(range);
560  newBlock->updateRange(range);
561  }
562 
563  // return the new generated block
564  return newBlock;
565 }
566 
568 {
569  // move cursors, do this first, now still lines() count is correct for target
570  for (TextCursor *cursor : m_cursors) {
571  cursor->m_line = cursor->lineInBlock() + targetBlock->lines();
572  cursor->m_block = targetBlock;
573  targetBlock->m_cursors.insert(cursor);
574  }
575  m_cursors.clear();
576 
577  // move lines
578  targetBlock->m_lines.reserve(targetBlock->lines() + lines());
579  for (size_t i = 0; i < m_lines.size(); ++i) {
580  targetBlock->m_lines.push_back(m_lines.at(i));
581  }
582  m_lines.clear();
583 
584  // fix ALL ranges!
585  const QList<TextRange *> allRanges = m_uncachedRanges.values() + m_cachedLineForRanges.keys();
586  for (TextRange *range : qAsConst(allRanges)) {
587  // update both blocks
588  updateRange(range);
589  targetBlock->updateRange(range);
590  }
591 }
592 
594 {
595  // kill cursors, if not belonging to a range
596  // we can do in-place editing of the current set of cursors as
597  // we remove them before deleting
598  for (auto it = m_cursors.begin(); it != m_cursors.end();) {
599  auto cursor = *it;
600  if (!cursor->kateRange()) {
601  // remove it and advance to next element
602  it = m_cursors.erase(it);
603 
604  // delete after cursor is gone from the set
605  // else the destructor will modify it!
606  delete cursor;
607  } else {
608  // keep this cursor
609  ++it;
610  }
611  }
612 
613  // kill lines
614  m_lines.clear();
615 }
616 
618 {
619  // move cursors, if not belonging to a range
620  // we can do in-place editing of the current set of cursors
621  for (auto it = m_cursors.begin(); it != m_cursors.end();) {
622  auto cursor = *it;
623  if (!cursor->kateRange()) {
624  cursor->m_column = 0;
625  cursor->m_line = 0;
626  cursor->m_block = targetBlock;
627  targetBlock->m_cursors.insert(cursor);
628 
629  // remove it and advance to next element
630  it = m_cursors.erase(it);
631  } else {
632  // keep this cursor
633  ++it;
634  }
635  }
636 
637  // kill lines
638  m_lines.clear();
639 }
640 
642 {
643  // mark all modified lines as saved
644  for (auto &textLine : m_lines) {
645  if (textLine->markedAsModified()) {
646  textLine->markAsSavedOnDisk(true);
647  }
648  }
649 }
650 
652 {
656  const int startLine = range->startInternal().lineInternal();
657  const int endLine = range->endInternal().lineInternal();
658  const bool isSingleLine = startLine == endLine;
659 
663  if ((endLine < m_startLine) || (startLine >= (m_startLine + lines()))) {
664  removeRange(range);
665  return;
666  }
667 
671  if (isSingleLine && m_cachedLineForRanges.contains(range) && (m_cachedLineForRanges.value(range) == startLine - m_startLine)) {
672  return;
673  }
674 
678  if (!isSingleLine && m_uncachedRanges.contains(range)) {
679  return;
680  }
681 
685  removeRange(range);
686 
690  if (!isSingleLine) {
694  m_uncachedRanges.insert(range);
695  return;
696  }
697 
701  const int lineOffset = startLine - m_startLine;
702 
706  if (m_cachedRangesForLine.size() <= lineOffset) {
707  m_cachedRangesForLine.resize(lineOffset + 1);
708  }
709 
713  m_cachedRangesForLine[lineOffset].insert(range);
714  m_cachedLineForRanges[range] = lineOffset;
715 }
716 
718 {
722  if (m_uncachedRanges.remove(range)) {
726  Q_ASSERT(!m_cachedLineForRanges.contains(range));
727  return;
728  }
729 
733  QHash<TextRange *, int>::iterator it = m_cachedLineForRanges.find(range);
734  if (it != m_cachedLineForRanges.end()) {
738  Q_ASSERT(!m_uncachedRanges.contains(range));
739 
743  Q_ASSERT(m_cachedRangesForLine.at(*it).contains(range));
744 
748  m_cachedRangesForLine[*it].remove(range);
749  m_cachedLineForRanges.erase(it);
750  return;
751  }
752 
756 }
757 
758 }
const TextCursor & startInternal() const
Non-virtual version of start(), which is faster.
QString & append(QChar ch)
void wrapLine(const KTextEditor::Cursor &position, int fixStartLinesStartIndex)
Wrap line at given cursor position.
QSharedPointer< T > create(Args &&...args)
void setStartLine(int startLine)
Set start line of this block.
void removeRange(TextRange *range)
Remove a range from this block.
void unwrapLine(int line, TextBlock *previousBlock, int fixStartLinesStartIndex)
Unwrap given line.
int lines() const
Number of lines in this block.
Definition: katetextblock.h:96
int size() const const
void push_back(const T &t)
QString & remove(int position, int n)
int lineInternal() const
Non-virtual version of line(), which is faster.
The Cursor represents a position in a Document.
Definition: cursor.h:85
void chop(int n)
int startLine() const
Start line of this block.
Definition: katetextblock.h:63
Class representing a single text line.
Definition: katetextline.h:39
void deleteBlockContent()
Delete the block content, delete all lines and delete all cursors not bound to ranges.
const TextCursor & endInternal() const
Nonvirtual version of end(), which is faster.
void debugPrint(int blockIndex) const
Debug output, print whole block content with line numbers and line length.
TextBlock(TextBuffer *buffer, int startLine)
Construct an empty text block.
~TextBlock()
Destruct the text block.
void updateRange(TextRange *range)
Update a range from this block.
void clearBlockContent(TextBlock *targetBlock)
Clear the block content, delete all lines, move all cursors not bound to range to given block at 0...
QString & insert(int position, QChar ch)
void markModifiedLinesAsSaved()
Flag all modified text lines as saved on disk.
constexpr int column() const Q_DECL_NOEXCEPT
Retrieve the column on which this cursor is situated.
Definition: cursor.h:217
constexpr Cursor start() const Q_DECL_NOEXCEPT
Get the start position of this range.
void removeText(const KTextEditor::Range &range, QString &removedText)
Remove text at given range.
QString right(int n) const const
An object representing a section of text, from one Cursor to another.
void appendLine(const QString &textOfLine)
Append a new line with given text.
void clearLines()
Clear the lines.
QString mid(int position, int n) const const
void mergeBlock(TextBlock *targetBlock)
Merge this block with given one, the given one must be a direct predecessor.
Class representing a &#39;clever&#39; text cursor.
constexpr Cursor end() const Q_DECL_NOEXCEPT
Get the end position of this range.
constexpr int line() const Q_DECL_NOEXCEPT
Retrieve the line on which this cursor is situated.
Definition: cursor.h:199
TextLine line(int line) const
Retrieve a text line.
Class representing a text buffer.
TextHistory & history()
TextHistory of this buffer.
void text(QString &text) const
Retrieve text of block.
Class representing a &#39;clever&#39; text range.
Definition: katetextrange.h:46
void insertText(const KTextEditor::Cursor &position, const QString &text)
Insert text at given cursor position.
Class representing a text block.
Definition: katetextblock.h:44
TextBlock * splitBlock(int fromLine)
Split given block.
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Sun Jul 5 2020 22:57:49 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.