KTextEditor

katetexthistory.cpp
1 /*
2  SPDX-FileCopyrightText: 2013 Christoph Cullmann <[email protected]>
3 
4  SPDX-License-Identifier: LGPL-2.0-or-later
5 */
6 
7 #include "katetexthistory.h"
8 #include "katetextbuffer.h"
9 
10 namespace Kate
11 {
12 TextHistory::TextHistory(TextBuffer &buffer)
13  : m_buffer(buffer)
14  , m_lastSavedRevision(-1)
15  , m_firstHistoryEntryRevision(0)
16 {
17  // just call clear to init
18  clear();
19 }
20 
21 TextHistory::~TextHistory()
22 {
23 }
24 
25 qint64 TextHistory::revision() const
26 {
27  // just output last revisions of buffer
28  return m_buffer.revision();
29 }
30 
31 void TextHistory::clear()
32 {
33  // reset last saved revision
34  m_lastSavedRevision = -1;
35 
36  // remove all history entries and add no-change dummy for first revision
37  m_historyEntries.clear();
38  m_historyEntries.push_back(Entry());
39 
40  // first entry will again belong to first revision
41  m_firstHistoryEntryRevision = 0;
42 }
43 
44 void TextHistory::setLastSavedRevision()
45 {
46  // current revision was successful saved
47  m_lastSavedRevision = revision();
48 }
49 
50 void TextHistory::wrapLine(const KTextEditor::Cursor &position)
51 {
52  // create and add new entry
53  Entry entry;
54  entry.type = Entry::WrapLine;
55  entry.line = position.line();
56  entry.column = position.column();
57  addEntry(entry);
58 }
59 
60 void TextHistory::unwrapLine(int line, int oldLineLength)
61 {
62  // create and add new entry
63  Entry entry;
64  entry.type = Entry::UnwrapLine;
65  entry.line = line;
66  entry.column = 0;
67  entry.oldLineLength = oldLineLength;
68  addEntry(entry);
69 }
70 
71 void TextHistory::insertText(const KTextEditor::Cursor &position, int length, int oldLineLength)
72 {
73  // create and add new entry
74  Entry entry;
75  entry.type = Entry::InsertText;
76  entry.line = position.line();
77  entry.column = position.column();
78  entry.length = length;
79  entry.oldLineLength = oldLineLength;
80  addEntry(entry);
81 }
82 
83 void TextHistory::removeText(const KTextEditor::Range &range, int oldLineLength)
84 {
85  // create and add new entry
86  Entry entry;
87  entry.type = Entry::RemoveText;
88  entry.line = range.start().line();
89  entry.column = range.start().column();
90  entry.length = range.end().column() - range.start().column();
91  entry.oldLineLength = oldLineLength;
92  addEntry(entry);
93 }
94 
95 void TextHistory::addEntry(const Entry &entry)
96 {
97  // history should never be empty
98  Q_ASSERT(!m_historyEntries.empty());
99 
100  // simple efficient check: if we only have one entry, and the entry is not referenced
101  // just replace it with the new one and adjust the revision
102  if ((m_historyEntries.size() == 1) && !m_historyEntries.front().referenceCounter) {
103  // remember new revision for first element, it is the revision we get after this change
104  m_firstHistoryEntryRevision = revision() + 1;
105 
106  // remember edit
107  m_historyEntries.front() = entry;
108 
109  // be done...
110  return;
111  }
112 
113  // ok, we have more than one entry or the entry is referenced, just add up new entries
114  m_historyEntries.push_back(entry);
115 }
116 
117 void TextHistory::lockRevision(qint64 revision)
118 {
119  // some invariants must hold
120  Q_ASSERT(!m_historyEntries.empty());
121  Q_ASSERT(revision >= m_firstHistoryEntryRevision);
122  Q_ASSERT(revision < (m_firstHistoryEntryRevision + qint64(m_historyEntries.size())));
123 
124  // increment revision reference counter
125  Entry &entry = m_historyEntries[revision - m_firstHistoryEntryRevision];
126  ++entry.referenceCounter;
127 }
128 
129 void TextHistory::unlockRevision(qint64 revision)
130 {
131  // some invariants must hold
132  Q_ASSERT(!m_historyEntries.empty());
133  Q_ASSERT(revision >= m_firstHistoryEntryRevision);
134  Q_ASSERT(revision < (m_firstHistoryEntryRevision + qint64(m_historyEntries.size())));
135 
136  // decrement revision reference counter
137  Entry &entry = m_historyEntries[revision - m_firstHistoryEntryRevision];
138  Q_ASSERT(entry.referenceCounter);
139  --entry.referenceCounter;
140 
141  // clean up no longer used revisions...
142  if (!entry.referenceCounter) {
143  // search for now unused stuff
144  qint64 unreferencedEdits = 0;
145  for (qint64 i = 0; i + 1 < qint64(m_historyEntries.size()); ++i) {
146  if (m_historyEntries[i].referenceCounter) {
147  break;
148  }
149 
150  // remember deleted count
151  ++unreferencedEdits;
152  }
153 
154  // remove unreferred from the list now
155  if (unreferencedEdits > 0) {
156  // remove stuff from history
157  m_historyEntries.erase(m_historyEntries.begin(), m_historyEntries.begin() + unreferencedEdits);
158 
159  // patch first entry revision
160  m_firstHistoryEntryRevision += unreferencedEdits;
161  }
162  }
163 }
164 
165 void TextHistory::Entry::transformCursor(int &cursorLine, int &cursorColumn, bool moveOnInsert) const
166 {
167  // simple stuff, sort out generic things
168 
169  // no change, if this change is in line behind cursor
170  if (line > cursorLine) {
171  return;
172  }
173 
174  // handle all history types
175  switch (type) {
176  // Wrap a line
177  case WrapLine:
178  // we wrap this line
179  if (cursorLine == line) {
180  // skip cursors with too small column
181  if (cursorColumn <= column) {
182  if (cursorColumn < column || !moveOnInsert) {
183  return;
184  }
185  }
186 
187  // adjust column
188  cursorColumn = cursorColumn - column;
189  }
190 
191  // always increment cursor line
192  cursorLine += 1;
193  return;
194 
195  // Unwrap a line
196  case UnwrapLine:
197  // we unwrap this line, adjust column
198  if (cursorLine == line) {
199  cursorColumn += oldLineLength;
200  }
201 
202  // decrease cursor line
203  cursorLine -= 1;
204  return;
205 
206  // Insert text
207  case InsertText:
208  // only interesting, if same line
209  if (cursorLine != line) {
210  return;
211  }
212 
213  // skip cursors with too small column
214  if (cursorColumn <= column)
215  if (cursorColumn < column || !moveOnInsert) {
216  return;
217  }
218 
219  // patch column of cursor
220  if (cursorColumn <= oldLineLength) {
221  cursorColumn += length;
222  }
223 
224  // special handling if cursor behind the real line, e.g. non-wrapping cursor in block selection mode
225  else if (cursorColumn < oldLineLength + length) {
226  cursorColumn = oldLineLength + length;
227  }
228 
229  return;
230 
231  // Remove text
232  case RemoveText:
233  // only interesting, if same line
234  if (cursorLine != line) {
235  return;
236  }
237 
238  // skip cursors with too small column
239  if (cursorColumn <= column) {
240  return;
241  }
242 
243  // patch column of cursor
244  if (cursorColumn <= column + length) {
245  cursorColumn = column;
246  } else {
247  cursorColumn -= length;
248  }
249 
250  return;
251 
252  // nothing
253  default:
254  return;
255  }
256 }
257 
258 void TextHistory::Entry::reverseTransformCursor(int &cursorLine, int &cursorColumn, bool moveOnInsert) const
259 {
260  // handle all history types
261  switch (type) {
262  // Wrap a line
263  case WrapLine:
264  // ignore this line
265  if (cursorLine <= line) {
266  return;
267  }
268 
269  // next line is unwrapped
270  if (cursorLine == line + 1) {
271  // adjust column
272  cursorColumn = cursorColumn + column;
273  }
274 
275  // always decrement cursor line
276  cursorLine -= 1;
277  return;
278 
279  // Unwrap a line
280  case UnwrapLine:
281  // ignore lines before unwrapped one
282  if (cursorLine < line - 1) {
283  return;
284  }
285 
286  // we unwrap this line, try to adjust cursor column if needed
287  if (cursorLine == line - 1) {
288  // skip cursors with to small columns
289  if (cursorColumn <= oldLineLength) {
290  if (cursorColumn < oldLineLength || !moveOnInsert) {
291  return;
292  }
293  }
294 
295  cursorColumn -= oldLineLength;
296  }
297 
298  // increase cursor line
299  cursorLine += 1;
300  return;
301 
302  // Insert text
303  case InsertText:
304  // only interesting, if same line
305  if (cursorLine != line) {
306  return;
307  }
308 
309  // skip cursors with too small column
310  if (cursorColumn <= column) {
311  return;
312  }
313 
314  // patch column of cursor
315  if (cursorColumn - length < column) {
316  cursorColumn = column;
317  } else {
318  cursorColumn -= length;
319  }
320 
321  return;
322 
323  // Remove text
324  case RemoveText:
325  // only interesting, if same line
326  if (cursorLine != line) {
327  return;
328  }
329 
330  // skip cursors with too small column
331  if (cursorColumn <= column)
332  if (cursorColumn < column || !moveOnInsert) {
333  return;
334  }
335 
336  // patch column of cursor
337  if (cursorColumn <= oldLineLength) {
338  cursorColumn += length;
339  }
340 
341  // special handling if cursor behind the real line, e.g. non-wrapping cursor in block selection mode
342  else if (cursorColumn < oldLineLength + length) {
343  cursorColumn = oldLineLength + length;
344  }
345  return;
346 
347  // nothing
348  default:
349  return;
350  }
351 }
352 
353 void TextHistory::transformCursor(int &line, int &column, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision)
354 {
355  // -1 special meaning for from/toRevision
356  if (fromRevision == -1) {
357  fromRevision = revision();
358  }
359 
360  if (toRevision == -1) {
361  toRevision = revision();
362  }
363 
364  // shortcut, same revision
365  if (fromRevision == toRevision) {
366  return;
367  }
368 
369  // some invariants must hold
370  Q_ASSERT(!m_historyEntries.empty());
371  Q_ASSERT(fromRevision != toRevision);
372  Q_ASSERT(fromRevision >= m_firstHistoryEntryRevision);
373  Q_ASSERT(fromRevision < (m_firstHistoryEntryRevision + qint64(m_historyEntries.size())));
374  Q_ASSERT(toRevision >= m_firstHistoryEntryRevision);
375  Q_ASSERT(toRevision < (m_firstHistoryEntryRevision + qint64(m_historyEntries.size())));
376 
377  // transform cursor
378  bool moveOnInsert = insertBehavior == KTextEditor::MovingCursor::MoveOnInsert;
379 
380  // forward or reverse transform?
381  if (toRevision > fromRevision) {
382  for (int rev = fromRevision - m_firstHistoryEntryRevision + 1; rev <= (toRevision - m_firstHistoryEntryRevision); ++rev) {
383  const Entry &entry = m_historyEntries.at(rev);
384  entry.transformCursor(line, column, moveOnInsert);
385  }
386  } else {
387  for (int rev = fromRevision - m_firstHistoryEntryRevision; rev >= (toRevision - m_firstHistoryEntryRevision + 1); --rev) {
388  const Entry &entry = m_historyEntries.at(rev);
389  entry.reverseTransformCursor(line, column, moveOnInsert);
390  }
391  }
392 }
393 
394 void TextHistory::transformRange(KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, KTextEditor::MovingRange::EmptyBehavior emptyBehavior, qint64 fromRevision, qint64 toRevision)
395 {
396  // invalidate on empty?
397  bool invalidateIfEmpty = emptyBehavior == KTextEditor::MovingRange::InvalidateIfEmpty;
398  if (invalidateIfEmpty && range.end() <= range.start()) {
399  range = KTextEditor::Range::invalid();
400  return;
401  }
402 
403  // -1 special meaning for from/toRevision
404  if (fromRevision == -1) {
405  fromRevision = revision();
406  }
407 
408  if (toRevision == -1) {
409  toRevision = revision();
410  }
411 
412  // shortcut, same revision
413  if (fromRevision == toRevision) {
414  return;
415  }
416 
417  // some invariants must hold
418  Q_ASSERT(!m_historyEntries.empty());
419  Q_ASSERT(fromRevision != toRevision);
420  Q_ASSERT(fromRevision >= m_firstHistoryEntryRevision);
421  Q_ASSERT(fromRevision < (m_firstHistoryEntryRevision + qint64(m_historyEntries.size())));
422  Q_ASSERT(toRevision >= m_firstHistoryEntryRevision);
423  Q_ASSERT(toRevision < (m_firstHistoryEntryRevision + qint64(m_historyEntries.size())));
424 
425  // transform cursors
426 
427  // first: copy cursors, without range association
428  int startLine = range.start().line(), startColumn = range.start().column(), endLine = range.end().line(), endColumn = range.end().column();
429 
430  bool moveOnInsertStart = !(insertBehaviors & KTextEditor::MovingRange::ExpandLeft);
431  bool moveOnInsertEnd = (insertBehaviors & KTextEditor::MovingRange::ExpandRight);
432 
433  // forward or reverse transform?
434  if (toRevision > fromRevision) {
435  for (int rev = fromRevision - m_firstHistoryEntryRevision + 1; rev <= (toRevision - m_firstHistoryEntryRevision); ++rev) {
436  const Entry &entry = m_historyEntries.at(rev);
437 
438  entry.transformCursor(startLine, startColumn, moveOnInsertStart);
439 
440  entry.transformCursor(endLine, endColumn, moveOnInsertEnd);
441 
442  // got empty?
443  if (endLine < startLine || (endLine == startLine && endColumn <= startColumn)) {
444  if (invalidateIfEmpty) {
445  range = KTextEditor::Range::invalid();
446  return;
447  } else {
448  // else normalize them
449  endLine = startLine;
450  endColumn = startColumn;
451  }
452  }
453  }
454  } else {
455  for (int rev = fromRevision - m_firstHistoryEntryRevision; rev >= (toRevision - m_firstHistoryEntryRevision + 1); --rev) {
456  const Entry &entry = m_historyEntries.at(rev);
457 
458  entry.reverseTransformCursor(startLine, startColumn, moveOnInsertStart);
459 
460  entry.reverseTransformCursor(endLine, endColumn, moveOnInsertEnd);
461 
462  // got empty?
463  if (endLine < startLine || (endLine == startLine && endColumn <= startColumn)) {
464  if (invalidateIfEmpty) {
465  range = KTextEditor::Range::invalid();
466  return;
467  } else {
468  // else normalize them
469  endLine = startLine;
470  endColumn = startColumn;
471  }
472  }
473  }
474  }
475 
476  // now, copy cursors back
477  range.setRange(KTextEditor::Cursor(startLine, startColumn), KTextEditor::Cursor(endLine, endColumn));
478 }
479 
480 }
void clear()
Clear the complete folding.
EmptyBehavior
Behavior of range if it becomes empty.
Definition: movingrange.h:165
The Cursor represents a position in a Document.
Definition: cursor.h:71
Expand to encapsulate new characters to the left of the range.
Definition: movingrange.h:155
InsertBehavior
Insert behavior of this cursor, should it stay if text is insert at its position or should it move...
Definition: movingcursor.h:64
void transformRange(KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, KTextEditor::MovingRange::EmptyBehavior emptyBehavior, qint64 fromRevision, qint64 toRevision=-1)
Transform a range from one revision to an other.
invalidate range, if it becomes empty
Definition: movingrange.h:167
constexpr int column() const Q_DECL_NOEXCEPT
Retrieve the column on which this cursor is situated.
Definition: cursor.h:203
constexpr Cursor start() const Q_DECL_NOEXCEPT
Get the start position of this range.
An object representing a section of text, from one Cursor to another.
void unlockRevision(qint64 revision)
Release a revision.
Expand to encapsulate new characters to the right of the range.
Definition: movingrange.h:157
qint64 revision() const
Current revision, just relay the revision of the buffer.
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:185
qint64 revision() const
Revision of this buffer.
static constexpr Range invalid() Q_DECL_NOEXCEPT
Returns an invalid range.
void setRange(const Range &range) Q_DECL_NOEXCEPT
Set the start and end cursors to range.start() and range.end() respectively.
void transformCursor(int &line, int &column, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision=-1)
Transform a cursor from one revision to an other.
void lockRevision(qint64 revision)
Lock a revision, this will keep it around until released again.
This file is part of the KDE documentation.
Documentation copyright © 1996-2020 The KDE developers.
Generated on Tue Sep 29 2020 23:05:11 by doxygen 1.8.11 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.