KTextEditor

kateundomanager.cpp
1/*
2 SPDX-FileCopyrightText: 2009-2010 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
3
4 SPDX-License-Identifier: LGPL-2.0-or-later
5*/
6
7#include "kateundomanager.h"
8
9#include <ktexteditor/view.h>
10
11#include "katedocument.h"
12#include "katepartdebug.h"
13#include "kateview.h"
14
15#include <QBitArray>
16
18 : QObject(doc)
19 , m_document(doc)
20{
21 connect(this, &KateUndoManager::undoEnd, this, &KateUndoManager::undoChanged);
22 connect(this, &KateUndoManager::redoEnd, this, &KateUndoManager::undoChanged);
23
24 connect(doc, &KTextEditor::DocumentPrivate::viewCreated, this, &KateUndoManager::viewCreated);
25
26 // Before reload save history
28 savedUndoItems = std::move(undoItems);
29 savedRedoItems = std::move(redoItems);
30 docChecksumBeforeReload = m_document->checksum();
31 });
32
33 // After reload restore it only if checksum of the doc is same
34 connect(doc, &KTextEditor::DocumentPrivate::loaded, this, [this](KTextEditor::Document *doc) {
35 if (doc && !doc->checksum().isEmpty() && !docChecksumBeforeReload.isEmpty() && doc->checksum() == docChecksumBeforeReload) {
36 undoItems = std::move(savedUndoItems);
37 redoItems = std::move(savedRedoItems);
38 Q_EMIT undoChanged();
39 }
40 docChecksumBeforeReload.clear();
41 savedUndoItems.clear();
42 savedRedoItems.clear();
43 });
44}
45
46KateUndoManager::~KateUndoManager() = default;
47
48void KateUndoManager::viewCreated(KTextEditor::Document *, KTextEditor::View *newView) const
49{
50 connect(newView, &KTextEditor::View::cursorPositionChanged, this, &KateUndoManager::undoCancel);
51}
52
54{
55 if (!m_isActive) {
56 return;
57 }
58
59 // editStart() and editEnd() must be called in alternating fashion
60 Q_ASSERT(!m_editCurrentUndo.has_value()); // make sure to enter a clean state
61
62 const KTextEditor::Cursor cursorPosition = activeView() ? activeView()->cursorPosition() : KTextEditor::Cursor::invalid();
63 const KTextEditor::Range primarySelectionRange = activeView() ? activeView()->selectionRange() : KTextEditor::Range::invalid();
65 if (activeView()) {
66 secondaryCursors = activeView()->plainSecondaryCursors();
67 }
68
69 // new current undo item
70 m_editCurrentUndo = KateUndoGroup(cursorPosition, primarySelectionRange, secondaryCursors);
71
72 Q_ASSERT(m_editCurrentUndo.has_value()); // a new undo group must be created by this method
73}
74
76{
77 if (!m_isActive) {
78 return;
79 }
80
81 // editStart() and editEnd() must be called in alternating fashion
82 Q_ASSERT(m_editCurrentUndo.has_value()); // an undo group must have been created by editStart()
83
84 const KTextEditor::Cursor cursorPosition = activeView() ? activeView()->cursorPosition() : KTextEditor::Cursor::invalid();
85 const KTextEditor::Range selectionRange = activeView() ? activeView()->selectionRange() : KTextEditor::Range::invalid();
86
88 if (activeView()) {
89 secondaryCursors = activeView()->plainSecondaryCursors();
90 }
91
92 m_editCurrentUndo->editEnd(cursorPosition, selectionRange, secondaryCursors);
93
94 bool changedUndo = false;
95
96 if (m_editCurrentUndo->isEmpty()) {
97 m_editCurrentUndo.reset();
98 } else if (!undoItems.empty() && undoItems.back().merge(&*m_editCurrentUndo, m_undoComplexMerge)) {
99 m_editCurrentUndo.reset();
100 } else {
101 undoItems.push_back(std::move(*m_editCurrentUndo));
102 changedUndo = true;
103 }
104
105 m_editCurrentUndo.reset();
106
107 if (changedUndo) {
108 Q_EMIT undoChanged();
109 }
110
111 Q_ASSERT(!m_editCurrentUndo.has_value()); // must be 0 after calling this method
112}
113
114void KateUndoManager::inputMethodStart()
115{
116 setActive(false);
117 m_document->editStart();
118}
119
120void KateUndoManager::inputMethodEnd()
121{
122 m_document->editEnd();
123 setActive(true);
124}
125
126void KateUndoManager::startUndo()
127{
128 setActive(false);
129 m_document->editStart();
130}
131
132void KateUndoManager::endUndo()
133{
134 m_document->editEnd();
135 setActive(true);
136}
137
138void KateUndoManager::slotTextInserted(int line, int col, const QString &s, const Kate::TextLine &tl)
139{
140 if (!m_editCurrentUndo.has_value() || s.isEmpty()) { // do we care about notifications?
141 return;
142 }
143
144 UndoItem item;
145 item.type = UndoItem::editInsertText;
146 item.line = line;
147 item.col = col;
148 item.text = s;
149 item.lineModFlags.setFlag(UndoItem::RedoLine1Modified);
150
151 if (tl.markedAsModified()) {
152 item.lineModFlags.setFlag(UndoItem::UndoLine1Modified);
153 } else {
154 item.lineModFlags.setFlag(UndoItem::UndoLine1Saved);
155 }
156 addUndoItem(std::move(item));
157}
158
159void KateUndoManager::slotTextRemoved(int line, int col, const QString &s, const Kate::TextLine &tl)
160{
161 if (!m_editCurrentUndo.has_value() || s.isEmpty()) { // do we care about notifications?
162 return;
163 }
164
165 UndoItem item;
166 item.type = UndoItem::editRemoveText;
167 item.line = line;
168 item.col = col;
169 item.text = s;
170 item.lineModFlags.setFlag(UndoItem::RedoLine1Modified);
171
172 if (tl.markedAsModified()) {
173 item.lineModFlags.setFlag(UndoItem::UndoLine1Modified);
174 } else {
175 item.lineModFlags.setFlag(UndoItem::UndoLine1Saved);
176 }
177 addUndoItem(std::move(item));
178}
179
180void KateUndoManager::slotMarkLineAutoWrapped(int line, bool autowrapped)
181{
182 if (m_editCurrentUndo.has_value()) { // do we care about notifications?
183 UndoItem item;
184 item.type = UndoItem::editMarkLineAutoWrapped;
185 item.line = line;
186 item.autowrapped = autowrapped;
187 addUndoItem(std::move(item));
188 }
189}
190
191void KateUndoManager::slotLineWrapped(int line, int col, int length, bool newLine, const Kate::TextLine &tl)
192{
193 if (!m_editCurrentUndo.has_value()) { // do we care about notifications?
194 return;
195 }
196
197 UndoItem item;
198 item.type = UndoItem::editWrapLine;
199 item.line = line;
200 item.col = col;
201 item.len = length;
202 item.newLine = newLine;
203
204 if (length > 0 || tl.markedAsModified()) {
205 item.lineModFlags.setFlag(UndoItem::RedoLine1Modified);
206 } else if (tl.markedAsSavedOnDisk()) {
207 item.lineModFlags.setFlag(UndoItem::RedoLine1Saved);
208 }
209
210 if (col > 0 || length == 0 || tl.markedAsModified()) {
211 item.lineModFlags.setFlag(UndoItem::RedoLine2Modified);
212 } else if (tl.markedAsSavedOnDisk()) {
213 item.lineModFlags.setFlag(UndoItem::RedoLine2Saved);
214 }
215
216 if (tl.markedAsModified()) {
217 item.lineModFlags.setFlag(UndoItem::UndoLine1Modified);
218 } else if ((length > 0 && col > 0) || tl.markedAsSavedOnDisk()) {
219 item.lineModFlags.setFlag(UndoItem::UndoLine1Saved);
220 }
221
222 addUndoItem(std::move(item));
223}
224
225void KateUndoManager::slotLineUnWrapped(int line, int col, int length, bool lineRemoved, const Kate::TextLine &tl, const Kate::TextLine &nextLine)
226{
227 if (!m_editCurrentUndo.has_value()) { // do we care about notifications?
228 return;
229 }
230
231 UndoItem item;
232 item.type = UndoItem::editUnWrapLine;
233 item.line = line;
234 item.col = col;
235 item.len = length;
236 item.removeLine = lineRemoved;
237
238 const int len1 = tl.length();
239 const int len2 = nextLine.length();
240
241 if (len1 > 0 && len2 > 0) {
242 item.lineModFlags.setFlag(UndoItem::RedoLine1Modified);
243
244 if (tl.markedAsModified()) {
245 item.lineModFlags.setFlag(UndoItem::UndoLine1Modified);
246 } else {
247 item.lineModFlags.setFlag(UndoItem::UndoLine1Saved);
248 }
249
250 if (nextLine.markedAsModified()) {
251 item.lineModFlags.setFlag(UndoItem::UndoLine2Modified);
252 } else {
253 item.lineModFlags.setFlag(UndoItem::UndoLine2Saved);
254 }
255 } else if (len1 == 0) {
256 if (nextLine.markedAsModified()) {
257 item.lineModFlags.setFlag(UndoItem::RedoLine1Modified);
258 } else if (nextLine.markedAsSavedOnDisk()) {
259 item.lineModFlags.setFlag(UndoItem::RedoLine1Saved);
260 }
261
262 if (tl.markedAsModified()) {
263 item.lineModFlags.setFlag(UndoItem::UndoLine1Modified);
264 } else {
265 item.lineModFlags.setFlag(UndoItem::UndoLine1Saved);
266 }
267
268 if (nextLine.markedAsModified()) {
269 item.lineModFlags.setFlag(UndoItem::UndoLine2Modified);
270 } else if (nextLine.markedAsSavedOnDisk()) {
271 item.lineModFlags.setFlag(UndoItem::UndoLine2Saved);
272 }
273 } else { // len2 == 0
274 if (nextLine.markedAsModified()) {
275 item.lineModFlags.setFlag(UndoItem::RedoLine1Modified);
276 } else if (nextLine.markedAsSavedOnDisk()) {
277 item.lineModFlags.setFlag(UndoItem::RedoLine1Saved);
278 }
279
280 if (tl.markedAsModified()) {
281 item.lineModFlags.setFlag(UndoItem::UndoLine1Modified);
282 } else if (tl.markedAsSavedOnDisk()) {
283 item.lineModFlags.setFlag(UndoItem::UndoLine1Saved);
284 }
285
286 if (nextLine.markedAsModified()) {
287 item.lineModFlags.setFlag(UndoItem::UndoLine2Modified);
288 } else {
289 item.lineModFlags.setFlag(UndoItem::UndoLine2Saved);
290 }
291 }
292
293 addUndoItem(std::move(item));
294}
295
297{
298 if (m_editCurrentUndo.has_value()) { // do we care about notifications?
299 UndoItem item;
300 item.type = UndoItem::editInsertLine;
301 item.line = line;
302 item.text = s;
303 item.lineModFlags.setFlag(UndoItem::RedoLine1Modified);
304 addUndoItem(std::move(item));
305 }
306}
307
308void KateUndoManager::slotLineRemoved(int line, const QString &s, const Kate::TextLine &tl)
309{
310 if (m_editCurrentUndo.has_value()) { // do we care about notifications?
311 UndoItem item;
312 item.type = UndoItem::editRemoveLine;
313 item.line = line;
314 item.text = s;
315 item.lineModFlags.setFlag(UndoItem::RedoLine1Modified);
316
317 if (tl.markedAsModified()) {
318 item.lineModFlags.setFlag(UndoItem::UndoLine1Modified);
319 } else {
320 item.lineModFlags.setFlag(UndoItem::UndoLine1Saved);
321 }
322 addUndoItem(std::move(item));
323 }
324}
325
326void KateUndoManager::undoCancel()
327{
328 // Don't worry about this when an edit is in progress
329 if (m_document->isEditRunning()) {
330 return;
331 }
332
334}
335
337{
338 if (!m_editCurrentUndo.has_value() && !undoItems.empty()) {
339 undoItems.back().safePoint();
340 } else if (m_editCurrentUndo.has_value()) {
341 m_editCurrentUndo.value().safePoint();
342 }
343}
344
345void KateUndoManager::addUndoItem(UndoItem undo)
346{
347 Q_ASSERT(m_editCurrentUndo.has_value()); // make sure there is an undo group for our item
348
349 m_editCurrentUndo->addItem(std::move(undo));
350
351 // Clear redo buffer
352 redoItems.clear();
353}
354
355void KateUndoManager::setActive(bool enabled)
356{
357 Q_ASSERT(!m_editCurrentUndo.has_value()); // must not already be in edit mode
358 Q_ASSERT(m_isActive != enabled);
359
360 m_isActive = enabled;
361
362 Q_EMIT isActiveChanged(enabled);
363}
364
366{
367 return undoItems.size();
368}
369
371{
372 return redoItems.size();
373}
374
376{
377 Q_ASSERT(!m_editCurrentUndo.has_value()); // undo is not supported while we care about notifications (call editEnd() first)
378
379 if (!undoItems.empty()) {
380 Q_EMIT undoStart(document());
381
382 undoItems.back().undo(this, activeView());
383 redoItems.push_back(std::move(undoItems.back()));
384 undoItems.pop_back();
385 updateModified();
386
387 Q_EMIT undoEnd(document());
388 }
389}
390
392{
393 Q_ASSERT(!m_editCurrentUndo.has_value()); // redo is not supported while we care about notifications (call editEnd() first)
394
395 if (!redoItems.empty()) {
396 Q_EMIT redoStart(document());
397
398 redoItems.back().redo(this, activeView());
399 undoItems.push_back(std::move(redoItems.back()));
400 redoItems.pop_back();
401 updateModified();
402
403 Q_EMIT redoEnd(document());
404 }
405}
406
407void KateUndoManager::updateModified()
408{
409 /*
410 How this works:
411
412 After noticing that there where to many scenarios to take into
413 consideration when using 'if's to toggle the "Modified" flag
414 I came up with this baby, flexible and repetitive calls are
415 minimal.
416
417 A numeric unique pattern is generated by toggling a set of bits,
418 each bit symbolizes a different state in the Undo Redo structure.
419
420 undoItems.isEmpty() != null BIT 1
421 redoItems.isEmpty() != null BIT 2
422 docWasSavedWhenUndoWasEmpty == true BIT 3
423 docWasSavedWhenRedoWasEmpty == true BIT 4
424 lastUndoGroupWhenSavedIsLastUndo BIT 5
425 lastUndoGroupWhenSavedIsLastRedo BIT 6
426 lastRedoGroupWhenSavedIsLastUndo BIT 7
427 lastRedoGroupWhenSavedIsLastRedo BIT 8
428
429 If you find a new pattern, please add it to the patterns array
430 */
431
432 unsigned char currentPattern = 0;
433 const unsigned char patterns[] = {5, 16, 21, 24, 26, 88, 90, 93, 133, 144, 149, 154, 165};
434 const unsigned char patternCount = sizeof(patterns);
435 KateUndoGroup *undoLast = nullptr;
436 KateUndoGroup *redoLast = nullptr;
437
438 if (undoItems.empty()) {
439 currentPattern |= 1;
440 } else {
441 undoLast = &undoItems.back();
442 }
443
444 if (redoItems.empty()) {
445 currentPattern |= 2;
446 } else {
447 redoLast = &redoItems.back();
448 }
449
450 if (docWasSavedWhenUndoWasEmpty) {
451 currentPattern |= 4;
452 }
453 if (docWasSavedWhenRedoWasEmpty) {
454 currentPattern |= 8;
455 }
456 if (lastUndoGroupWhenSaved == undoLast) {
457 currentPattern |= 16;
458 }
459 if (lastUndoGroupWhenSaved == redoLast) {
460 currentPattern |= 32;
461 }
462 if (lastRedoGroupWhenSaved == undoLast) {
463 currentPattern |= 64;
464 }
465 if (lastRedoGroupWhenSaved == redoLast) {
466 currentPattern |= 128;
467 }
468
469 // This will print out the pattern information
470
471 qCDebug(LOG_KTE) << "Pattern:" << static_cast<unsigned int>(currentPattern);
472
473 for (uint patternIndex = 0; patternIndex < patternCount; ++patternIndex) {
474 if (currentPattern == patterns[patternIndex]) {
475 // Note: m_document->setModified() calls KateUndoManager::setModified!
476 m_document->setModified(false);
477 // (dominik) whenever the doc is not modified, succeeding edits
478 // should not be merged
480 qCDebug(LOG_KTE) << "setting modified to false!";
481 break;
482 }
483 }
484}
485
486void KateUndoManager::clearUndo()
487{
488 undoItems.clear();
489
490 lastUndoGroupWhenSaved = nullptr;
491 docWasSavedWhenUndoWasEmpty = false;
492
493 Q_EMIT undoChanged();
494}
495
496void KateUndoManager::clearRedo()
497{
498 redoItems.clear();
499
500 lastRedoGroupWhenSaved = nullptr;
501 docWasSavedWhenRedoWasEmpty = false;
502
503 Q_EMIT undoChanged();
504}
505
506void KateUndoManager::setModified(bool modified)
507{
508 if (!modified) {
509 if (!undoItems.empty()) {
510 lastUndoGroupWhenSaved = &undoItems.back();
511 }
512
513 if (!redoItems.empty()) {
514 lastRedoGroupWhenSaved = &redoItems.back();
515 }
516
517 docWasSavedWhenUndoWasEmpty = undoItems.empty();
518 docWasSavedWhenRedoWasEmpty = redoItems.empty();
519 }
520}
521
522void KateUndoManager::updateLineModifications()
523{
524 // change LineSaved flag of all undo & redo items to LineModified
525 for (KateUndoGroup &undoGroup : undoItems) {
526 undoGroup.flagSavedAsModified();
527 }
528
529 for (KateUndoGroup &undoGroup : redoItems) {
530 undoGroup.flagSavedAsModified();
531 }
532
533 // iterate all undo/redo items to find out, which item sets the flag LineSaved
534 QBitArray lines(document()->lines(), false);
535 for (int i = undoItems.size() - 1; i >= 0; --i) {
536 undoItems[i].markRedoAsSaved(lines);
537 }
538
539 lines.fill(false);
540 for (int i = redoItems.size() - 1; i >= 0; --i) {
541 redoItems[i].markUndoAsSaved(lines);
542 }
543}
544
546{
547 Q_ASSERT(!m_editCurrentUndo.has_value());
548 if (!undoItems.empty()) {
549 KateUndoGroup &last = undoItems.back();
550 last.setUndoCursor(undoCursor);
551 last.setRedoCursor(redoCursor);
552 }
553}
554
556{
557 Q_ASSERT(!m_editCurrentUndo.has_value());
558 if (!undoItems.empty()) {
559 undoItems.back().redoCursor();
560 }
562}
563
564void KateUndoManager::updateConfig()
565{
566 Q_EMIT undoChanged();
567}
568
570{
571 m_undoComplexMerge = allow;
572}
573
574KTextEditor::ViewPrivate *KateUndoManager::activeView()
575{
576 return static_cast<KTextEditor::ViewPrivate *>(m_document->activeView());
577}
578
579#include "moc_kateundomanager.cpp"
The Cursor represents a position in a Document.
Definition cursor.h:75
static constexpr Cursor invalid() noexcept
Returns an invalid cursor.
Definition cursor.h:112
Backend of KTextEditor::Document related public KTextEditor interfaces.
QByteArray checksum() const override
Returns a git compatible sha1 checksum of this document on disk.
bool editStart()
Enclose editor actions with editStart() and editEnd() to group them.
bool editEnd()
End a editor operation.
A KParts derived class representing a text document.
Definition document.h:284
void viewCreated(KTextEditor::Document *document, KTextEditor::View *view)
This signal is emitted whenever the document creates a new view.
void aboutToReload(KTextEditor::Document *document)
Warn anyone listening that the current document is about to reload.
An object representing a section of text, from one Cursor to another.
static constexpr Range invalid() noexcept
Returns an invalid range.
A text widget with KXMLGUIClient that represents a Document.
Definition view.h:244
void cursorPositionChanged(KTextEditor::View *view, KTextEditor::Cursor newPosition)
This signal is emitted whenever the view's cursor position changed.
Class to manage a group of undo items.
Definition kateundo.h:59
void setRedoCursor(const KTextEditor::Cursor cursor)
Set the redo cursor to cursor.
Definition kateundo.h:130
void setUndoCursor(const KTextEditor::Cursor cursor)
Set the undo cursor to cursor.
Definition kateundo.h:122
void slotLineInserted(int line, const QString &s)
Notify KateUndoManager that a line was inserted.
void slotLineUnWrapped(int line, int col, int length, bool lineRemoved, const Kate::TextLine &tl, const Kate::TextLine &nextLine)
Notify KateUndoManager that a line was un-wrapped.
void slotLineRemoved(int line, const QString &s, const Kate::TextLine &tl)
Notify KateUndoManager that a line was removed.
KateUndoManager(KTextEditor::DocumentPrivate *doc)
Creates a clean undo history.
void setUndoRedoCursorsOfLastGroup(const KTextEditor::Cursor undoCursor, const KTextEditor::Cursor redoCursor)
Used by the swap file recovery, this function afterwards manipulates the undo/redo cursors of the las...
void setAllowComplexMerge(bool allow)
Allows or disallows merging of "complex" undo groups.
void slotTextRemoved(int line, int col, const QString &s, const Kate::TextLine &tl)
Notify KateUndoManager that text was removed.
void redo()
Redo the latest undo group.
KTEXTEDITOR_EXPORT void undoSafePoint()
Prevent latest KateUndoGroup from being merged with the next one.
KTextEditor::Cursor lastRedoCursor() const
Returns the redo cursor of the last undo group.
void slotMarkLineAutoWrapped(int line, bool autowrapped)
Notify KateUndoManager that a line was marked as autowrapped.
void slotTextInserted(int line, int col, const QString &s, const Kate::TextLine &tl)
Notify KateUndoManager that text was inserted.
uint redoCount() const
Returns how many redo() actions can be performed.
void undo()
Undo the latest undo group.
uint undoCount() const
Returns how many undo() actions can be performed.
void editEnd()
Notify KateUndoManager about the end of an edit.
void slotLineWrapped(int line, int col, int length, bool newLine, const Kate::TextLine &tl)
Notify KateUndoManager that a line was wrapped.
void editStart()
Notify KateUndoManager about the beginning of an edit.
Class representing a single text line.
int length() const
Returns the line's length.
void clear()
bool isEmpty() const const
QFlags< T > & setFlag(Enum flag, bool on)
Q_EMITQ_EMIT
QMetaObject::Connection connect(const QObject *sender, PointerToMemberFunction signal, Functor functor)
bool isEmpty() const const
This file is part of the KDE documentation.
Documentation copyright © 1996-2024 The KDE developers.
Generated on Fri Jul 12 2024 11:53:31 by doxygen 1.11.0 written by Dimitri van Heesch, © 1997-2006

KDE's Doxygen guidelines are available online.