1
/* This file is part of the Kate project.
3
* Copyright (C) 2010 Christoph Cullmann <cullmann@kde.org>
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.
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.
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.
21
#include "katetexthistory.h"
22
#include "katetextbuffer.h"
26
TextHistory::TextHistory (TextBuffer &buffer)
28
, m_lastSavedRevision (-1)
29
, m_firstHistoryEntryRevision (0)
31
// just call clear to init
35
TextHistory::~TextHistory ()
39
qint64 TextHistory::revision () const
41
// just output last revisions of buffer
42
return m_buffer.revision ();
45
void TextHistory::clear ()
47
// reset last saved revision
48
m_lastSavedRevision = -1;
50
// remove all history entries and add no-change dummy for first revision
51
m_historyEntries.clear ();
52
m_historyEntries.push_back (Entry ());
54
// first entry will again belong to first revision
55
m_firstHistoryEntryRevision = 0;
58
void TextHistory::setLastSavedRevision ()
60
// current revision was successful saved
61
m_lastSavedRevision = revision ();
64
void TextHistory::wrapLine (const KTextEditor::Cursor &position)
66
// create and add new entry
68
entry.type = Entry::WrapLine;
69
entry.line = position.line ();
70
entry.column = position.column ();
74
void TextHistory::unwrapLine (int line, int oldLineLength)
76
// create and add new entry
78
entry.type = Entry::UnwrapLine;
81
entry.oldLineLength = oldLineLength;
85
void TextHistory::insertText (const KTextEditor::Cursor &position, int length, int oldLineLength)
87
// create and add new entry
89
entry.type = Entry::InsertText;
90
entry.line = position.line ();
91
entry.column = position.column ();
92
entry.length = length;
93
entry.oldLineLength = oldLineLength;
97
void TextHistory::removeText (const KTextEditor::Range &range, int oldLineLength)
99
// create and add new entry
101
entry.type = Entry::RemoveText;
102
entry.line = range.start().line ();
103
entry.column = range.start().column ();
104
entry.length = range.end().column() - range.start().column();
105
entry.oldLineLength = oldLineLength;
109
void TextHistory::addEntry (const Entry &entry)
112
* history should never be empty
114
Q_ASSERT (!m_historyEntries.empty ());
117
* simple efficient check: if we only have one entry, and the entry is not referenced
118
* just replace it with the new one and adjust the revision
120
if ((m_historyEntries.size () == 1) && !m_historyEntries.first().referenceCounter) {
122
* remember new revision for first element, it is the revision we get after this change
124
m_firstHistoryEntryRevision = revision () + 1;
129
m_historyEntries.first() = entry;
138
* ok, we have more than one entry or the entry is referenced, just add up new entries
140
m_historyEntries.push_back (entry);
143
void TextHistory::lockRevision (qint64 revision)
146
* some invariants must hold
148
Q_ASSERT (!m_historyEntries.empty ());
149
Q_ASSERT (revision >= m_firstHistoryEntryRevision);
150
Q_ASSERT (revision < (m_firstHistoryEntryRevision + m_historyEntries.size()));
153
* increment revision reference counter
155
Entry &entry = m_historyEntries[revision - m_firstHistoryEntryRevision];
156
++entry.referenceCounter;
159
void TextHistory::unlockRevision (qint64 revision)
162
* some invariants must hold
164
Q_ASSERT (!m_historyEntries.empty ());
165
Q_ASSERT (revision >= m_firstHistoryEntryRevision);
166
Q_ASSERT (revision < (m_firstHistoryEntryRevision + m_historyEntries.size()));
169
* decrement revision reference counter
171
Entry &entry = m_historyEntries[revision - m_firstHistoryEntryRevision];
172
Q_ASSERT (entry.referenceCounter);
173
--entry.referenceCounter;
176
* clean up no longer used revisions...
178
if (!entry.referenceCounter) {
180
* search for now unused stuff
182
int unreferencedEdits = 0;
183
for (int i = 0; i + 1 < m_historyEntries.size(); ++i) {
184
if (m_historyEntries.at(i).referenceCounter)
187
// remember deleted count
192
* remove unrefed from the list now
194
if (unreferencedEdits > 0) {
195
// remove stuff from history
196
m_historyEntries.erase (m_historyEntries.begin(), m_historyEntries.begin() + unreferencedEdits);
198
// patch first entry revision
199
m_firstHistoryEntryRevision += unreferencedEdits;
204
void TextHistory::Entry::transformCursor (int &cursorLine, int &cursorColumn, bool moveOnInsert) const
207
* simple stuff, sort out generic things
211
* no change, if this change is in line behind cursor
213
if (line > cursorLine)
217
* handle all history types
227
if (cursorLine == line) {
229
* skip cursors with too small column
231
if (cursorColumn <= column) {
232
if (cursorColumn < column || !moveOnInsert)
239
cursorColumn = cursorColumn - column;
243
* always increment cursor line
253
* we unwrap this line, adjust column
255
if (cursorLine == line)
256
cursorColumn += oldLineLength;
259
* decrease cursor line
269
* only interesting, if same line
271
if (cursorLine != line)
274
// skip cursors with too small column
275
if (cursorColumn <= column)
276
if (cursorColumn < column || !moveOnInsert)
279
// patch column of cursor
280
if (cursorColumn <= oldLineLength)
281
cursorColumn += length;
283
// special handling if cursor behind the real line, e.g. non-wrapping cursor in block selection mode
284
else if (cursorColumn < oldLineLength + length)
285
cursorColumn = oldLineLength + length;
294
* only interesting, if same line
296
if (cursorLine != line)
299
// skip cursors with too small column
300
if (cursorColumn <= column)
303
// patch column of cursor
304
if (cursorColumn <= column + length)
305
cursorColumn = column;
307
cursorColumn -= length;
319
void TextHistory::Entry::reverseTransformCursor (int &cursorLine, int &cursorColumn, bool moveOnInsert) const
322
* handle all history types
332
if (cursorLine <= line)
336
* next line is unwrapped
338
if (cursorLine == line + 1) {
342
cursorColumn = cursorColumn + column;
346
* always decrement cursor line
356
* ignore lines before unwrapped one
358
if (cursorLine < line - 1)
362
* we unwrap this line, try to adjust cursor column if needed
364
if (cursorLine == line - 1) {
366
* skip cursors with to small columns
368
if (cursorColumn <= oldLineLength) {
369
if (cursorColumn < oldLineLength || !moveOnInsert)
373
cursorColumn -= oldLineLength;
377
* increase cursor line
387
* only interesting, if same line
389
if (cursorLine != line)
392
// skip cursors with too small column
393
if (cursorColumn <= column)
396
// patch column of cursor
397
if (cursorColumn - length < column)
398
cursorColumn = column;
400
cursorColumn -= length;
409
* only interesting, if same line
411
if (cursorLine != line)
414
// skip cursors with too small column
415
if (cursorColumn <= column)
416
if (cursorColumn < column || !moveOnInsert)
419
// patch column of cursor
420
if (cursorColumn <= oldLineLength)
421
cursorColumn += length;
423
// special handling if cursor behind the real line, e.g. non-wrapping cursor in block selection mode
424
else if (cursorColumn < oldLineLength + length)
425
cursorColumn = oldLineLength + length;
436
void TextHistory::transformCursor (int& line, int& column, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision)
439
* -1 special meaning for from/toRevision
441
if (fromRevision == -1)
442
fromRevision = revision ();
444
if (toRevision == -1)
445
toRevision = revision ();
448
* shortcut, same revision
450
if (fromRevision == toRevision)
454
* some invariants must hold
456
Q_ASSERT (!m_historyEntries.empty ());
457
Q_ASSERT (fromRevision != toRevision);
458
Q_ASSERT (fromRevision >= m_firstHistoryEntryRevision);
459
Q_ASSERT (fromRevision < (m_firstHistoryEntryRevision + m_historyEntries.size()));
460
Q_ASSERT (toRevision >= m_firstHistoryEntryRevision);
461
Q_ASSERT (toRevision < (m_firstHistoryEntryRevision + m_historyEntries.size()));
466
bool moveOnInsert = insertBehavior == KTextEditor::MovingCursor::MoveOnInsert;
469
* forward or reverse transform?
471
if (toRevision > fromRevision) {
472
for (int rev = fromRevision - m_firstHistoryEntryRevision + 1; rev <= (toRevision - m_firstHistoryEntryRevision); ++rev) {
473
const Entry &entry = m_historyEntries.at(rev);
474
entry.transformCursor (line, column, moveOnInsert);
477
for (int rev = fromRevision - m_firstHistoryEntryRevision; rev >= (toRevision - m_firstHistoryEntryRevision + 1); --rev) {
478
const Entry &entry = m_historyEntries.at(rev);
479
entry.reverseTransformCursor (line, column, moveOnInsert);
484
void TextHistory::transformRange (KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, KTextEditor::MovingRange::EmptyBehavior emptyBehavior, qint64 fromRevision, qint64 toRevision)
487
* invalidate on empty?
489
bool invalidateIfEmpty = emptyBehavior == KTextEditor::MovingRange::InvalidateIfEmpty;
490
if (invalidateIfEmpty && range.end() <= range.start()) {
491
range = KTextEditor::Range::invalid();
496
* -1 special meaning for from/toRevision
498
if (fromRevision == -1)
499
fromRevision = revision ();
501
if (toRevision == -1)
502
toRevision = revision ();
505
* shortcut, same revision
507
if (fromRevision == toRevision)
511
* some invariants must hold
513
Q_ASSERT (!m_historyEntries.empty ());
514
Q_ASSERT (fromRevision != toRevision);
515
Q_ASSERT (fromRevision >= m_firstHistoryEntryRevision);
516
Q_ASSERT (fromRevision < (m_firstHistoryEntryRevision + m_historyEntries.size()));
517
Q_ASSERT (toRevision >= m_firstHistoryEntryRevision);
518
Q_ASSERT (toRevision < (m_firstHistoryEntryRevision + m_historyEntries.size()));
524
// first: copy cursors, without range association
525
int startLine = range.start().line(), startColumn = range.start().column(), endLine = range.end().line(), endColumn = range.end().column();
527
bool moveOnInsertStart = !(insertBehaviors & KTextEditor::MovingRange::ExpandLeft);
528
bool moveOnInsertEnd = (insertBehaviors & KTextEditor::MovingRange::ExpandRight);
531
* forward or reverse transform?
533
if (toRevision > fromRevision) {
534
for (int rev = fromRevision - m_firstHistoryEntryRevision + 1; rev <= (toRevision - m_firstHistoryEntryRevision); ++rev) {
535
const Entry &entry = m_historyEntries.at(rev);
537
entry.transformCursor (startLine, startColumn, moveOnInsertStart);
539
entry.transformCursor (endLine, endColumn, moveOnInsertEnd);
542
if(endLine < startLine || (endLine == startLine && endColumn <= startColumn))
544
if (invalidateIfEmpty) {
545
range = KTextEditor::Range::invalid();
549
// else normalize them
551
endColumn = startColumn;
556
for (int rev = fromRevision - m_firstHistoryEntryRevision ; rev >= (toRevision - m_firstHistoryEntryRevision + 1); --rev) {
557
const Entry &entry = m_historyEntries.at(rev);
559
entry.reverseTransformCursor (startLine, startColumn, moveOnInsertStart);
561
entry.reverseTransformCursor (endLine, endColumn, moveOnInsertEnd);
564
if(endLine < startLine || (endLine == startLine && endColumn <= startColumn))
566
if (invalidateIfEmpty) {
567
range = KTextEditor::Range::invalid();
571
// else normalize them
573
endColumn = startColumn;
579
// now, copy cursors back
580
range.start().setLine(startLine);
581
range.start().setColumn(startColumn);
582
range.end().setLine(endLine);
583
range.end().setColumn(endColumn);