1/* This file is part of the Kate project.
2 *
3 * Copyright (C) 2010 Christoph Cullmann <cullmann@kde.org>
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
24namespace Kate {
25
26TextBlock::TextBlock (TextBuffer *buffer, int startLine)
27 : m_buffer (buffer)
28 , m_startLine (startLine)
29{
30 // reserve the block size
31 m_lines.reserve (m_buffer->m_blockSize);
32}
33
34TextBlock::~TextBlock ()
35{
36 // blocks should be empty before they are deleted!
37 Q_ASSERT (m_lines.empty());
38 Q_ASSERT (m_cursors.empty());
39
40 // it only is a hint for ranges for this block, not the storage of them
41}
42
43void TextBlock::setStartLine (int startLine)
44{
45 // allow only valid lines
46 Q_ASSERT (startLine >= 0);
47 Q_ASSERT (startLine < m_buffer->lines ());
48
49 m_startLine = startLine;
50}
51
52TextLine TextBlock::line (int line) const
53{
54 // right input
55 Q_ASSERT (line >= startLine ());
56
57 // calc internal line
58 line = line - startLine ();
59
60 // in range
61 Q_ASSERT (line < m_lines.size ());
62
63 // get text line
64 return m_lines.at(line);
65}
66
67void TextBlock::appendLine (const QString &textOfLine)
68{
69 m_lines.append (TextLine (new TextLineData(textOfLine)));
70}
71
72void TextBlock::clearLines ()
73{
74 m_lines.clear ();
75}
76
77void TextBlock::text (QString &text) const
78{
79 // combine all lines
80 for (int i = 0; i < m_lines.size(); ++i) {
81 // not first line, insert \n
82 if (i > 0 || startLine() > 0)
83 text.append ('\n');
84
85 text.append (m_lines.at(i)->text ());
86 }
87}
88
89void TextBlock::wrapLine (const KTextEditor::Cursor &position, int fixStartLinesStartIndex)
90{
91 // calc internal line
92 int line = position.line () - startLine ();
93
94 // get text
95 QString &text = m_lines.at(line)->textReadWrite ();
96
97 // check if valid column
98 Q_ASSERT (position.column() >= 0);
99 Q_ASSERT (position.column() <= text.size());
100
101 // create new line and insert it
102 m_lines.insert (m_lines.begin() + line + 1, TextLine (new TextLineData()));
103
104 // cases for modification:
105 // 1. line is wrapped in the middle
106 // 2. if empty line is wrapped, mark new line as modified
107 // 3. line-to-be-wrapped is already modified
108 if (position.column() > 0 || text.size() == 0 || m_lines.at(line)->markedAsModified()) {
109 m_lines.at(line + 1)->markAsModified(true);
110 } else if (m_lines.at(line)->markedAsSavedOnDisk()) {
111 m_lines.at(line + 1)->markAsSavedOnDisk(true);
112 }
113
114 // perhaps remove some text from previous line and append it
115 if (position.column() < text.size ()) {
116 // text from old line moved first to new one
117 m_lines.at(line+1)->textReadWrite() = text.right (text.size() - position.column());
118
119 // now remove wrapped text from old line
120 text.chop (text.size() - position.column());
121
122 // mark line as modified
123 m_lines.at(line)->markAsModified(true);
124 }
125
126 /**
127 * fix all start lines
128 * we need to do this NOW, else the range update will FAIL!
129 * bug 313759
130 */
131 m_buffer->fixStartLines (fixStartLinesStartIndex);
132
133 /**
134 * notify the text history
135 */
136 m_buffer->history().wrapLine (position);
137
138 /**
139 * cursor and range handling below
140 */
141
142 // no cursors will leave or join this block
143
144 // no cursors in this block, no work to do..
145 if (m_cursors.empty())
146 return;
147
148 // move all cursors on the line which has the text inserted
149 // remember all ranges modified
150 QSet<TextRange *> changedRanges;
151 foreach (TextCursor *cursor, m_cursors) {
152 // skip cursors on lines in front of the wrapped one!
153 if (cursor->lineInBlock() < line)
154 continue;
155
156 // either this is simple, line behind the wrapped one
157 if (cursor->lineInBlock() > line) {
158 // patch line of cursor
159 cursor->m_line++;
160 }
161
162 // this is the wrapped line
163 else {
164 // skip cursors with too small column
165 if (cursor->column() <= position.column()) {
166 if (cursor->column() < position.column() || !cursor->m_moveOnInsert)
167 continue;
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
180 if (cursor->kateRange())
181 changedRanges.insert (cursor->kateRange());
182 }
183
184 // check validity of all ranges, might invalidate them...
185 foreach (TextRange *range, changedRanges)
186 range->checkValidity ();
187}
188
189void TextBlock::unwrapLine (int line, TextBlock *previousBlock, int fixStartLinesStartIndex)
190{
191 // calc internal line
192 line = line - startLine ();
193
194 // two possiblities: either first line of this block or later line
195 if (line == 0) {
196 // we need previous block with at least one line
197 Q_ASSERT (previousBlock);
198 Q_ASSERT (previousBlock->lines () > 0);
199
200 // move last line of previous block to this one, might result in empty block
201 TextLine oldFirst = m_lines.at(0);
202 int lastLineOfPreviousBlock = previousBlock->lines ()-1;
203 TextLine newFirst = previousBlock->m_lines.last();
204 m_lines[0] = newFirst;
205 previousBlock->m_lines.erase (previousBlock->m_lines.begin() + (previousBlock->lines () - 1));
206
207 const int oldSizeOfPreviousLine = newFirst->text().size();
208 if (oldFirst->length() > 0) {
209 // append text
210 newFirst->textReadWrite().append (oldFirst->text());
211
212 // mark line as modified, since text was appended
213 newFirst->markAsModified(true);
214 }
215
216 // patch startLine of this block
217 --m_startLine;
218
219 /**
220 * fix all start lines
221 * we need to do this NOW, else the range update will FAIL!
222 * bug 313759
223 */
224 m_buffer->fixStartLines (fixStartLinesStartIndex);
225
226 /**
227 * notify the text history in advance
228 */
229 m_buffer->history().unwrapLine (startLine () + line, oldSizeOfPreviousLine);
230
231 /**
232 * cursor and range handling below
233 */
234
235 // no cursors in this block and the previous one, no work to do..
236 if (m_cursors.empty() && previousBlock->m_cursors.empty())
237 return;
238
239 // move all cursors because of the unwrapped line
240 // remember all ranges modified
241 QSet<TextRange *> changedRanges;
242 foreach (TextCursor *cursor, m_cursors) {
243 // this is the unwrapped line
244 if (cursor->lineInBlock() == 0) {
245 // patch column
246 cursor->m_column += oldSizeOfPreviousLine;
247
248 // remember range, if any
249 if (cursor->kateRange())
250 changedRanges.insert (cursor->kateRange());
251 }
252 }
253
254 // move cursors of the moved line from previous block to this block now
255 QSet<TextCursor *> newPreviousCursors;
256 foreach (TextCursor *cursor, previousBlock->m_cursors) {
257 if (cursor->lineInBlock() == lastLineOfPreviousBlock) {
258 cursor->m_line = 0;
259 cursor->m_block = this;
260 m_cursors.insert (cursor);
261
262 // remember range, if any
263 if (cursor->kateRange())
264 changedRanges.insert (cursor->kateRange());
265 }
266 else
267 newPreviousCursors.insert (cursor);
268 }
269 previousBlock->m_cursors = newPreviousCursors;
270
271 // fixup the ranges that might be effected, because they moved from last line to this block
272 foreach (TextRange *range, changedRanges) {
273 // update both blocks
274 updateRange (range);
275 previousBlock->updateRange (range);
276 }
277
278 // check validity of all ranges, might invalidate them...
279 foreach (TextRange *range, changedRanges)
280 range->checkValidity ();
281
282 // be done
283 return;
284 }
285
286 // easy: just move text to previous line and remove current one
287 const int oldSizeOfPreviousLine = m_lines.at(line-1)->length();
288 const int sizeOfCurrentLine = m_lines.at(line)->length();
289 if (sizeOfCurrentLine > 0)
290 m_lines.at(line-1)->textReadWrite().append (m_lines.at(line)->text());
291
292 const bool lineChanged = (oldSizeOfPreviousLine > 0 && m_lines.at(line - 1)->markedAsModified())
293 || (sizeOfCurrentLine > 0 && (oldSizeOfPreviousLine > 0 || m_lines.at(line)->markedAsModified()));
294 m_lines.at(line-1)->markAsModified(lineChanged);
295 if (oldSizeOfPreviousLine == 0 && m_lines.at(line)->markedAsSavedOnDisk())
296 m_lines.at(line-1)->markAsSavedOnDisk(true);
297
298 m_lines.erase (m_lines.begin () + line);
299
300 /**
301 * fix all start lines
302 * we need to do this NOW, else the range update will FAIL!
303 * bug 313759
304 */
305 m_buffer->fixStartLines (fixStartLinesStartIndex);
306
307 /**
308 * notify the text history in advance
309 */
310 m_buffer->history().unwrapLine (startLine () + line, oldSizeOfPreviousLine);
311
312 /**
313 * cursor and range handling below
314 */
315
316 // no cursors in this block, no work to do..
317 if (m_cursors.empty())
318 return;
319
320 // move all cursors because of the unwrapped line
321 // remember all ranges modified
322 QSet<TextRange *> changedRanges;
323 foreach (TextCursor *cursor, m_cursors) {
324 // skip cursors in lines in front of removed one
325 if (cursor->lineInBlock() < line)
326 continue;
327
328 // this is the unwrapped line
329 if (cursor->lineInBlock() == line) {
330 // patch column
331 cursor->m_column += oldSizeOfPreviousLine;
332 }
333
334 // patch line of cursor
335 cursor->m_line--;
336
337 // remember range, if any
338 if (cursor->kateRange())
339 changedRanges.insert (cursor->kateRange());
340 }
341
342 // check validity of all ranges, might invalidate them...
343 foreach (TextRange *range, changedRanges)
344 range->checkValidity ();
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)->textReadWrite ();
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 /**
365 * notify the text history
366 */
367 m_buffer->history().insertText (position, text.size(), oldLength);
368
369 /**
370 * cursor and range handling below
371 */
372
373 // no cursors in this block, no work to do..
374 if (m_cursors.empty())
375 return;
376
377 // move all cursors on the line which has the text inserted
378 // remember all ranges modified
379 QSet<TextRange *> changedRanges;
380 foreach (TextCursor *cursor, m_cursors) {
381 // skip cursors not on this line!
382 if (cursor->lineInBlock() != line)
383 continue;
384
385 // skip cursors with too small column
386 if (cursor->column() <= position.column()) {
387 if (cursor->column() < position.column() || !cursor->m_moveOnInsert)
388 continue;
389 }
390
391 // patch column of cursor
392 if (cursor->m_column <= oldLength)
393 cursor->m_column += text.size ();
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 // remember range, if any
400 if (cursor->kateRange())
401 changedRanges.insert (cursor->kateRange());
402 }
403
404 // check validity of all ranges, might invalidate them...
405 foreach (TextRange *range, changedRanges)
406 range->checkValidity ();
407}
408
409void TextBlock::removeText (const KTextEditor::Range &range, QString &removedText)
410{
411 // calc internal line
412 int line = range.start().line () - startLine ();
413
414 // get text
415 QString &textOfLine = m_lines.at(line)->textReadWrite ();
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 /**
432 * notify the text history
433 */
434 m_buffer->history().removeText (range, oldLength);
435
436 /**
437 * cursor and range handling below
438 */
439
440 // no cursors in this block, no work to do..
441 if (m_cursors.empty())
442 return;
443
444 // move all cursors on the line which has the text removed
445 // remember all ranges modified
446 QSet<TextRange *> changedRanges;
447 foreach (TextCursor *cursor, m_cursors) {
448 // skip cursors not on this line!
449 if (cursor->lineInBlock() != line)
450 continue;
451
452 // skip cursors with too small column
453 if (cursor->column() <= range.start().column())
454 continue;
455
456 // patch column of cursor
457 if (cursor->column() <= range.end().column())
458 cursor->m_column = range.start().column ();
459 else
460 cursor->m_column -= (range.end().column() - range.start().column());
461
462 // remember range, if any
463 if (cursor->kateRange())
464 changedRanges.insert (cursor->kateRange());
465 }
466
467 // check validity of all ranges, might invalidate them...
468 foreach (TextRange *range, changedRanges)
469 range->checkValidity ();
470}
471
472void TextBlock::debugPrint (int blockIndex) const
473{
474 // print all blocks
475 for (int i = 0; i < m_lines.size(); ++i)
476 printf ("%4d - %4d : %4d : '%s'\n", blockIndex, startLine() + i
477 , m_lines.at(i)->text().size(), qPrintable (m_lines.at(i)->text()));
478}
479
480TextBlock *TextBlock::splitBlock (int fromLine)
481{
482 // half the block
483 int linesOfNewBlock = lines () - fromLine;
484
485 // create and insert new block
486 TextBlock *newBlock = new TextBlock (m_buffer, startLine() + fromLine);
487
488 // move lines
489 newBlock->m_lines.reserve (linesOfNewBlock);
490 for (int i = fromLine; i < m_lines.size(); ++i)
491 newBlock->m_lines.append (m_lines.at(i));
492 m_lines.resize (fromLine);
493
494 // move cursors
495 QSet<TextCursor*> oldBlockSet;
496 foreach (TextCursor *cursor, m_cursors) {
497 if (cursor->lineInBlock() >= fromLine) {
498 cursor->m_line = cursor->lineInBlock() - fromLine;
499 cursor->m_block = newBlock;
500 newBlock->m_cursors.insert (cursor);
501 }
502 else
503 oldBlockSet.insert (cursor);
504 }
505 m_cursors = oldBlockSet;
506
507 // fix ALL ranges!
508 QList<TextRange*> allRanges = m_uncachedRanges.toList() + m_cachedLineForRanges.keys();
509 foreach (TextRange *range, allRanges) {
510 // update both blocks
511 updateRange (range);
512 newBlock->updateRange (range);
513 }
514
515 // return the new generated block
516 return newBlock;
517}
518
519void TextBlock::mergeBlock (TextBlock *targetBlock)
520{
521 // move cursors, do this first, now still lines() count is correct for target
522 foreach (TextCursor *cursor, m_cursors) {
523 cursor->m_line = cursor->lineInBlock() + targetBlock->lines ();
524 cursor->m_block = targetBlock;
525 targetBlock->m_cursors.insert (cursor);
526 }
527 m_cursors.clear ();
528
529 // move lines
530 targetBlock->m_lines.reserve (targetBlock->lines() + lines ());
531 for (int i = 0; i < m_lines.size(); ++i)
532 targetBlock->m_lines.append (m_lines.at(i));
533 m_lines.clear ();
534
535 // fix ALL ranges!
536 QList<TextRange*> allRanges = m_uncachedRanges.toList() + m_cachedLineForRanges.keys();
537 foreach(TextRange* range, allRanges) {
538 // update both blocks
539 updateRange (range);
540 targetBlock->updateRange (range);
541 }
542}
543
544void TextBlock::deleteBlockContent ()
545{
546 // kill cursors, if not belonging to a range
547 QSet<TextCursor *> copy = m_cursors;
548 foreach (TextCursor *cursor, copy)
549 if (!cursor->kateRange())
550 delete cursor;
551
552 // kill lines
553 m_lines.clear ();
554}
555
556void TextBlock::clearBlockContent (TextBlock *targetBlock)
557{
558 // move cursors, if not belonging to a range
559 QSet<TextCursor *> copy = m_cursors;
560 foreach (TextCursor *cursor, copy) {
561 if (!cursor->kateRange()) {
562 cursor->m_column = 0;
563 cursor->m_line = 0;
564 cursor->m_block = targetBlock;
565 targetBlock->m_cursors.insert (cursor);
566 m_cursors.remove (cursor);
567 }
568 }
569
570 // kill lines
571 m_lines.clear ();
572}
573
574void TextBlock::markModifiedLinesAsSaved ()
575{
576 // mark all modified lines as saved
577 for (int i = 0; i < m_lines.size(); ++i) {
578 TextLine textLine = m_lines[i];
579 if (textLine->markedAsModified())
580 textLine->markAsSavedOnDisk(true);
581 }
582}
583
584void TextBlock::updateRange (TextRange* range)
585{
586 /**
587 * get some simple facts about our nice range
588 */
589 const int startLine = range->startInternal().lineInternal();
590 const int endLine = range->endInternal().lineInternal();
591 const bool isSingleLine = startLine == endLine;
592
593 /**
594 * perhaps remove range and be done
595 */
596 if ((endLine < m_startLine) || (startLine >= (m_startLine + lines()))) {
597 removeRange (range);
598 return;
599 }
600
601 /**
602 * The range is still a single-line range, and is still cached to the correct line.
603 */
604 if(isSingleLine && m_cachedLineForRanges.contains (range) && (m_cachedLineForRanges.value(range) == startLine - m_startLine))
605 return;
606
607 /**
608 * The range is still a multi-line range, and is already in the correct set.
609 */
610 if(!isSingleLine && m_uncachedRanges.contains (range))
611 return;
612
613 /**
614 * remove, if already there!
615 */
616 removeRange(range);
617
618 /**
619 * simple case: multi-line range
620 */
621 if (!isSingleLine) {
622 /**
623 * The range cannot be cached per line, as it spans multiple lines
624 */
625 m_uncachedRanges.insert(range);
626 return;
627 }
628
629 /**
630 * The range is contained by a single line, put it into the line-cache
631 */
632 const int lineOffset = startLine - m_startLine;
633
634 /**
635 * enlarge cache if needed
636 */
637 if (m_cachedRangesForLine.size() <= lineOffset)
638 m_cachedRangesForLine.resize(lineOffset+1);
639
640 /**
641 * insert into mapping
642 */
643 m_cachedRangesForLine[lineOffset].insert(range);
644 m_cachedLineForRanges[range] = lineOffset;
645}
646
647void TextBlock::removeRange (TextRange* range)
648{
649 /**
650 * uncached range? remove it and be done
651 */
652 if(m_uncachedRanges.remove (range)) {
653 /**
654 * must be only uncached!
655 */
656 Q_ASSERT (!m_cachedLineForRanges.contains(range));
657 return;
658 }
659
660 /**
661 * cached range?
662 */
663 QHash<TextRange*, int>::iterator it = m_cachedLineForRanges.find(range);
664 if (it != m_cachedLineForRanges.end()) {
665 /**
666 * must be only cached!
667 */
668 Q_ASSERT (!m_uncachedRanges.contains(range));
669
670 /**
671 * query the range from cache, must be there
672 */
673 Q_ASSERT (m_cachedRangesForLine.at(*it).contains(range));
674
675 /**
676 * remove it and be done
677 */
678 m_cachedRangesForLine[*it].remove(range);
679 m_cachedLineForRanges.erase(it);
680 return;
681 }
682
683 /**
684 * else: range was not for this block, just do nothing, removeRange should be "safe" to use
685 */
686}
687
688}
689