unwrapLine implemented cursor moving, more complex than I would like it, this needs...
[kate:kate.git] / playground / katetextblock.cpp
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
24 namespace Kate {
25
26 TextBlock::TextBlock (TextBuffer *buffer, int startLine)
27   : m_buffer (buffer)
28   , m_startLine (startLine)
29 {
30 }
31
32 TextBlock::~TextBlock ()
33 {
34   // blocks should be empty before they are deleted!
35   Q_ASSERT (m_lines.empty());
36   Q_ASSERT (m_cursors.empty());
37 }
38
39 void TextBlock::setStartLine (int startLine)
40 {
41   // allow only valid lines
42   Q_ASSERT (startLine >= 0);
43   Q_ASSERT (startLine < m_buffer->lines ());
44
45   m_startLine = startLine;
46 }
47
48 TextLine TextBlock::line (int line) const
49 {
50   // calc internal line
51   line = line - startLine ();
52
53   // get text line
54   return m_lines[line];
55 }
56
57 void TextBlock::text (QString &text) const
58 {
59   // combine all lines
60   for (int i = 0; i < m_lines.size(); ++i) {
61       // not first line, insert \n
62       if (i > 0 || startLine() > 0)
63         text.append ('\n');
64
65       text.append (m_lines[i]->text ());
66   }
67 }
68
69 void TextBlock::wrapLine (const KTextEditor::Cursor &position)
70 {
71   // calc internal line
72   int line = position.line () - startLine ();
73
74   // get text
75   QString &text = m_lines[line]->textReadWrite ();
76
77   // check if valid column
78   Q_ASSERT (position.column() >= 0);
79   Q_ASSERT (position.column() <= text.size());
80
81   // create new line and insert it
82   m_lines.insert (m_lines.begin() + line + 1, TextLine (new TextLineData()));
83
84   // perhaps remove some text from previous line and append it
85   if (position.column() < text.size ()) {
86     // text from old line moved first to new one
87     m_lines[line+1]->textReadWrite() = text.right (text.size() - position.column());
88
89     // now remove wrapped text from old line
90     text.chop (text.size() - position.column());
91   }
92
93   /**
94    * cursor and range handling below
95    */
96
97   // no cursors in this block, no work to do..
98   if (m_cursors.empty())
99     return;
100
101   // move all cursors on the line which has the text inserted
102   // remember all ranges modified
103   QSet<TextRange *> changedRanges;
104   foreach (TextCursor *cursor, m_cursors) {
105       // skip cursors on lines in front of the wrapped one!
106       if (cursor->lineInBlock() < line)
107         continue;
108
109       // either this is simple, line behind the wrapped one
110       if (cursor->lineInBlock() > line) {
111         // patch line of cursor
112         cursor->m_line++;
113       }
114
115       // this is the wrapped line
116       else {
117         // skip cursors with too small column
118         if (cursor->column() <= position.column()) {
119           if (cursor->column() < position.column() || !cursor->m_moveOnInsert)
120             continue;
121         }
122
123         // move cursor
124
125         // patch line of cursor
126         cursor->m_line++;
127
128         // patch column
129         cursor->m_column -= position.column();
130       }
131
132       // remember range, if any
133       if (cursor->range())
134         changedRanges.insert (cursor->range());
135   }
136
137   // check validity of all ranges, might invalidate them...
138   foreach (TextRange *range, changedRanges)
139     range->checkValidity ();
140 }
141
142 void TextBlock::unwrapLine (int line, TextBlock *previousBlock)
143 {
144   // calc internal line
145   line = line - startLine ();
146
147   // two possiblities: either first line of this block or later line
148   if (line == 0) {
149     // we need previous block with at least one line
150     Q_ASSERT (previousBlock);
151     Q_ASSERT (previousBlock->lines () > 0);
152
153     // move last line of previous block to this one, might result in empty block
154     TextLine oldFirst = m_lines[0];
155     int lastLineOfPreviousBlock = previousBlock->lines ()-1;
156     m_lines[0] = previousBlock->m_lines.last();
157     previousBlock->m_lines.erase (previousBlock->m_lines.begin() + (previousBlock->lines () - 1));
158
159     // append text
160     int oldSizeOfPreviousLine = m_lines[0]->text().size();
161     m_lines[0]->textReadWrite().append (oldFirst->text());
162
163     // patch startLine of this block
164     --m_startLine;
165
166     /**
167     * cursor and range handling below
168     */
169
170     // no cursors in this and previous block, no work to do..
171     if (previousBlock->m_cursors.empty() && m_cursors.empty())
172       return;
173
174     // move all cursors because of the unwrapped line
175     // remember all ranges modified
176     QSet<TextRange *> changedRanges;
177     foreach (TextCursor *cursor, m_cursors) {
178         // this is the unwrapped line
179         if (cursor->lineInBlock() == 0) {
180           // patch column
181           cursor->m_column += oldSizeOfPreviousLine;
182         }
183
184         // remember range, if any
185         if (cursor->range())
186           changedRanges.insert (cursor->range());
187     }
188
189     // move cursors of the moved line from previous block to this block now
190     QSet<TextCursor *> newPreviousCursors;
191     foreach (TextCursor *cursor, previousBlock->m_cursors) {
192       if (cursor->lineInBlock() == lastLineOfPreviousBlock) {
193         cursor->m_line = 0;
194         cursor->m_block = this;
195         m_cursors.insert (cursor);
196       }
197       else
198         newPreviousCursors.insert (cursor);
199     }
200     previousBlock->m_cursors = newPreviousCursors;
201
202     // check validity of all ranges, might invalidate them...
203     foreach (TextRange *range, changedRanges)
204       range->checkValidity ();
205
206     // be done
207     return;
208   }
209
210   // easy: just move text to previous line and remove current one
211   int oldSizeOfPreviousLine = m_lines[line-1]->text().size();
212   m_lines[line-1]->textReadWrite().append (m_lines[line]->text());
213   m_lines.erase (m_lines.begin () + line);
214
215   /**
216    * cursor and range handling below
217    */
218
219   // no cursors in this block, no work to do..
220   if (m_cursors.empty())
221     return;
222
223   // move all cursors because of the unwrapped line
224   // remember all ranges modified
225   QSet<TextRange *> changedRanges;
226   foreach (TextCursor *cursor, m_cursors) {
227       // skip cursors in lines in front of removed one
228       if (cursor->lineInBlock() < line)
229         continue;
230
231       // patch line of cursor
232       cursor->m_line--;
233
234       // this is the unwrapped line
235       if (cursor->lineInBlock() == line) {
236         // patch column
237         cursor->m_column += oldSizeOfPreviousLine;
238       }
239
240       // remember range, if any
241       if (cursor->range())
242         changedRanges.insert (cursor->range());
243   }
244
245   // check validity of all ranges, might invalidate them...
246   foreach (TextRange *range, changedRanges)
247     range->checkValidity ();
248 }
249
250 void TextBlock::insertText (const KTextEditor::Cursor &position, const QString &text)
251 {
252   // calc internal line
253   int line = position.line () - startLine ();
254
255   // get text
256   QString &textOfLine = m_lines[line]->textReadWrite ();
257
258   // check if valid column
259   Q_ASSERT (position.column() >= 0);
260   Q_ASSERT (position.column() <= textOfLine.size());
261
262   // insert text
263   textOfLine.insert (position.column(), text);
264
265   /**
266    * cursor and range handling below
267    */
268
269   // no cursors in this block, no work to do..
270   if (m_cursors.empty())
271     return;
272
273   // move all cursors on the line which has the text inserted
274   // remember all ranges modified
275   QSet<TextRange *> changedRanges;
276   foreach (TextCursor *cursor, m_cursors) {
277       // skip cursors not on this line!
278       if (cursor->lineInBlock() != line)
279         continue;
280
281       // skip cursors with too small column
282       if (cursor->column() <= position.column()) {
283         if (cursor->column() < position.column() || !cursor->m_moveOnInsert)
284           continue;
285       }
286
287       // patch column of cursor
288       cursor->m_column += text.size ();
289
290       // remember range, if any
291       if (cursor->range())
292         changedRanges.insert (cursor->range());
293   }
294
295   // check validity of all ranges, might invalidate them...
296   foreach (TextRange *range, changedRanges)
297     range->checkValidity ();
298 }
299
300 void TextBlock::removeText (const KTextEditor::Range &range, QString &removedText)
301 {
302   // calc internal line
303   int line = range.start().line () - startLine ();
304
305   // get text
306   QString &textOfLine = m_lines[line]->textReadWrite ();
307
308   // check if valid column
309   Q_ASSERT (range.start().column() >= 0);
310   Q_ASSERT (range.start().column() <= textOfLine.size());
311   Q_ASSERT (range.end().column() >= 0);
312   Q_ASSERT (range.end().column() <= textOfLine.size());
313
314   // get text which will be removed
315   removedText = textOfLine.mid (range.start().column(), range.end().column() - range.start().column());
316
317   // remove text
318   textOfLine.remove (range.start().column(), range.end().column() - range.start().column());
319
320   /**
321    * cursor and range handling below
322    */
323
324   // no cursors in this block, no work to do..
325   if (m_cursors.empty())
326     return;
327
328   // move all cursors on the line which has the text removed
329   // remember all ranges modified
330   QSet<TextRange *> changedRanges;
331   foreach (TextCursor *cursor, m_cursors) {
332       // skip cursors not on this line!
333       if (cursor->lineInBlock() != line)
334         continue;
335
336       // skip cursors with too small column
337       if (cursor->column() <= range.start().column())
338           continue;
339
340       // patch column of cursor
341       if (cursor->column() <= range.end().column())
342         cursor->m_column = range.start().column ();
343       else
344         cursor->m_column -= (range.end().column() - range.start().column());
345
346       // remember range, if any
347       if (cursor->range())
348         changedRanges.insert (cursor->range());
349   }
350
351   // check validity of all ranges, might invalidate them...
352   foreach (TextRange *range, changedRanges)
353     range->checkValidity ();
354 }
355
356 void TextBlock::debugPrint (int blockIndex) const
357 {
358   // print all blocks
359   for (int i = 0; i < m_lines.size(); ++i)
360     printf ("%4d - %4d : %4d : '%s'\n", blockIndex, startLine() + i
361       , m_lines[i]->text().size(), qPrintable (m_lines[i]->text()));
362 }
363
364 TextBlock *TextBlock::splitBlock (int fromLine)
365 {
366   // half the block
367   int linesOfNewBlock = lines () - fromLine;
368
369   // create and insert new block
370   TextBlock *newBlock = new TextBlock (m_buffer, startLine() + fromLine);
371
372   // move lines
373   newBlock->m_lines.reserve (linesOfNewBlock);
374   for (int i = fromLine; i < m_lines.size(); ++i)
375     newBlock->m_lines.append (m_lines[i]);
376   m_lines.resize (fromLine);
377
378   // move cursors
379   QSet<TextCursor*> oldBlockSet;
380   foreach (TextCursor *cursor, m_cursors) {
381       if (cursor->lineInBlock() >= fromLine) {
382         cursor->m_line = cursor->lineInBlock() - fromLine;
383         cursor->m_block = newBlock;
384         newBlock->m_cursors.insert (cursor);
385       }
386       else
387         oldBlockSet.insert (cursor);
388   }
389   m_cursors = oldBlockSet;
390
391   // return the new generated block
392   return newBlock;
393 }
394
395 void TextBlock::mergeBlock (TextBlock *targetBlock)
396 {
397    // move cursors, do this first, now still lines() count is correct for target
398   foreach (TextCursor *cursor, m_cursors) {
399     cursor->m_line = cursor->lineInBlock() + targetBlock->lines ();
400     cursor->m_block = targetBlock;
401     targetBlock->m_cursors.insert (cursor);
402   }
403   m_cursors.clear ();
404
405   // move lines
406   targetBlock->m_lines.reserve (targetBlock->lines() + lines ());
407   for (int i = 0; i < m_lines.size(); ++i)
408     targetBlock->m_lines.append (m_lines[i]);
409 }
410
411 void TextBlock::deleteBlockContent ()
412 {
413   // kill cursors, if not belonging to a range
414   QSet<TextCursor *> copy = m_cursors;
415   foreach (TextCursor *cursor, copy)
416     if (!cursor->range())
417       delete cursor;
418
419   // kill lines
420   m_lines.clear ();
421 }
422
423 void TextBlock::clearBlockContent (TextBlock *targetBlock)
424 {
425   // move cursors, if not belonging to a range
426   QSet<TextCursor *> copy = m_cursors;
427   foreach (TextCursor *cursor, copy) {
428     if (!cursor->range()) {
429       cursor->m_column = 0;
430       cursor->m_line = 0;
431       cursor->m_block = targetBlock;
432       targetBlock->m_cursors.insert (cursor);
433       m_cursors.remove (cursor);
434     }
435   }
436
437   // kill lines
438   m_lines.clear ();
439 }
440
441 }