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

source code of ktexteditor/src/buffer/katetexthistory.cpp