KTextEditor

katetexthistory.cpp
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(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(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(m_historyEntries.begin(), 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(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
394void TextHistory::transformRange(KTextEditor::Range &range,
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()) {
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(rev);
444
445 entry.transformCursor(startLine, startColumn, moveOnInsertStart);
446
447 entry.transformCursor(endLine, endColumn, moveOnInsertEnd);
448
449 // got empty?
450 if (endLine < startLine || (endLine == startLine && endColumn <= startColumn)) {
451 if (invalidateIfEmpty) {
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(rev);
464
465 entry.reverseTransformCursor(startLine, startColumn, moveOnInsertStart);
466
467 entry.reverseTransformCursor(endLine, endColumn, moveOnInsertEnd);
468
469 // got empty?
470 if (endLine < startLine || (endLine == startLine && endColumn <= startColumn)) {
471 if (invalidateIfEmpty) {
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(KTextEditor::Cursor(startLine, startColumn), KTextEditor::Cursor(endLine, endColumn));
485}
486
487}
The Cursor represents a position in a Document.
Definition cursor.h:75
constexpr int column() const noexcept
Retrieve the column on which this cursor is situated.
Definition cursor.h:192
constexpr int line() const noexcept
Retrieve the line on which this cursor is situated.
Definition cursor.h:174
InsertBehavior
Insert behavior of this cursor, should it stay if text is insert at its position or should it move.
@ MoveOnInsert
move on insert
EmptyBehavior
Behavior of range if it becomes empty.
@ InvalidateIfEmpty
invalidate range, if it becomes empty
@ ExpandRight
Expand to encapsulate new characters to the right of the range.
@ ExpandLeft
Expand to encapsulate new characters to the left of the range.
An object representing a section of text, from one Cursor to another.
constexpr Cursor end() const noexcept
Get the end position of this range.
constexpr Cursor start() const noexcept
Get the start position of this range.
void setRange(Range range) noexcept
Set the start and end cursors to range.start() and range.end() respectively.
static constexpr Range invalid() noexcept
Returns an invalid range.
KGuiItem clear()
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Tue Mar 26 2024 11:15:43 by doxygen 1.10.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.