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

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