File indexing completed on 2024-04-28 11:45:29

0001 /*
0002     SPDX-FileCopyrightText: 2009-2010 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "kateundomanager.h"
0008 
0009 #include <ktexteditor/view.h>
0010 
0011 #include "katedocument.h"
0012 #include "katemodifiedundo.h"
0013 #include "katepartdebug.h"
0014 #include "kateview.h"
0015 
0016 #include <QBitArray>
0017 
0018 KateUndoManager::KateUndoManager(KTextEditor::DocumentPrivate *doc)
0019     : QObject(doc)
0020     , m_document(doc)
0021 {
0022     connect(this, &KateUndoManager::undoEnd, this, &KateUndoManager::undoChanged);
0023     connect(this, &KateUndoManager::redoEnd, this, &KateUndoManager::undoChanged);
0024 
0025     connect(doc, &KTextEditor::DocumentPrivate::viewCreated, this, &KateUndoManager::viewCreated);
0026 
0027     // Before reload save history
0028     connect(doc, &KTextEditor::DocumentPrivate::aboutToReload, this, [this] {
0029         savedUndoItems = undoItems;
0030         savedRedoItems = redoItems;
0031         undoItems.clear();
0032         redoItems.clear();
0033         docChecksumBeforeReload = m_document->checksum();
0034     });
0035 
0036     // After reload restore it only if checksum of the doc is same
0037     connect(doc, &KTextEditor::DocumentPrivate::loaded, this, [this](KTextEditor::Document *doc) {
0038         if (doc && !doc->checksum().isEmpty() && !docChecksumBeforeReload.isEmpty() && doc->checksum() == docChecksumBeforeReload) {
0039             undoItems = savedUndoItems;
0040             redoItems = savedRedoItems;
0041             Q_EMIT undoChanged();
0042         } else {
0043             // Else delete everything, we don't want to leak
0044             qDeleteAll(savedUndoItems);
0045             qDeleteAll(savedRedoItems);
0046         }
0047         docChecksumBeforeReload.clear();
0048         savedUndoItems.clear();
0049         savedRedoItems.clear();
0050     });
0051 }
0052 
0053 KateUndoManager::~KateUndoManager()
0054 {
0055     delete m_editCurrentUndo;
0056 
0057     // cleanup the undo/redo items, very important, truee :/
0058     qDeleteAll(undoItems);
0059     undoItems.clear();
0060     qDeleteAll(redoItems);
0061     redoItems.clear();
0062 }
0063 
0064 KTextEditor::Document *KateUndoManager::document()
0065 {
0066     return m_document;
0067 }
0068 
0069 void KateUndoManager::viewCreated(KTextEditor::Document *, KTextEditor::View *newView) const
0070 {
0071     connect(newView, &KTextEditor::View::cursorPositionChanged, this, &KateUndoManager::undoCancel);
0072 }
0073 
0074 void KateUndoManager::editStart()
0075 {
0076     if (!m_isActive) {
0077         return;
0078     }
0079 
0080     // editStart() and editEnd() must be called in alternating fashion
0081     Q_ASSERT(m_editCurrentUndo == nullptr); // make sure to enter a clean state
0082 
0083     const KTextEditor::Cursor cursorPosition = activeView() ? activeView()->cursorPosition() : KTextEditor::Cursor::invalid();
0084     const KTextEditor::Range primarySelectionRange = activeView() ? activeView()->selectionRange() : KTextEditor::Range::invalid();
0085     QVector<KTextEditor::ViewPrivate::PlainSecondaryCursor> secondaryCursors;
0086     if (activeView()) {
0087         secondaryCursors = activeView()->plainSecondaryCursors();
0088     }
0089 
0090     // new current undo item
0091     m_editCurrentUndo = new KateUndoGroup(this, cursorPosition, primarySelectionRange, secondaryCursors);
0092 
0093     Q_ASSERT(m_editCurrentUndo != nullptr); // a new undo group must be created by this method
0094 }
0095 
0096 void KateUndoManager::editEnd()
0097 {
0098     if (!m_isActive) {
0099         return;
0100     }
0101 
0102     // editStart() and editEnd() must be called in alternating fashion
0103     Q_ASSERT(m_editCurrentUndo != nullptr); // an undo group must have been created by editStart()
0104 
0105     const KTextEditor::Cursor cursorPosition = activeView() ? activeView()->cursorPosition() : KTextEditor::Cursor::invalid();
0106     const KTextEditor::Range selectionRange = activeView() ? activeView()->selectionRange() : KTextEditor::Range::invalid();
0107 
0108     QVector<KTextEditor::ViewPrivate::PlainSecondaryCursor> secondaryCursors;
0109     if (activeView()) {
0110         secondaryCursors = activeView()->plainSecondaryCursors();
0111     }
0112 
0113     m_editCurrentUndo->editEnd(cursorPosition, selectionRange, secondaryCursors);
0114 
0115     bool changedUndo = false;
0116 
0117     if (m_editCurrentUndo->isEmpty()) {
0118         delete m_editCurrentUndo;
0119     } else if (!undoItems.isEmpty() && undoItems.last()->merge(m_editCurrentUndo, m_undoComplexMerge)) {
0120         delete m_editCurrentUndo;
0121     } else {
0122         undoItems.append(m_editCurrentUndo);
0123         changedUndo = true;
0124     }
0125 
0126     m_editCurrentUndo = nullptr;
0127 
0128     if (changedUndo) {
0129         Q_EMIT undoChanged();
0130     }
0131 
0132     Q_ASSERT(m_editCurrentUndo == nullptr); // must be 0 after calling this method
0133 }
0134 
0135 void KateUndoManager::inputMethodStart()
0136 {
0137     setActive(false);
0138     m_document->editStart();
0139 }
0140 
0141 void KateUndoManager::inputMethodEnd()
0142 {
0143     m_document->editEnd();
0144     setActive(true);
0145 }
0146 
0147 void KateUndoManager::startUndo()
0148 {
0149     setActive(false);
0150     m_document->editStart();
0151 }
0152 
0153 void KateUndoManager::endUndo()
0154 {
0155     m_document->editEnd();
0156     setActive(true);
0157 }
0158 
0159 void KateUndoManager::slotTextInserted(int line, int col, const QString &s)
0160 {
0161     if (m_editCurrentUndo != nullptr) { // do we care about notifications?
0162         addUndoItem(new KateModifiedInsertText(m_document, line, col, s));
0163     }
0164 }
0165 
0166 void KateUndoManager::slotTextRemoved(int line, int col, const QString &s)
0167 {
0168     if (m_editCurrentUndo != nullptr) { // do we care about notifications?
0169         addUndoItem(new KateModifiedRemoveText(m_document, line, col, s));
0170     }
0171 }
0172 
0173 void KateUndoManager::slotMarkLineAutoWrapped(int line, bool autowrapped)
0174 {
0175     if (m_editCurrentUndo != nullptr) { // do we care about notifications?
0176         addUndoItem(new KateEditMarkLineAutoWrappedUndo(m_document, line, autowrapped));
0177     }
0178 }
0179 
0180 void KateUndoManager::slotLineWrapped(int line, int col, int length, bool newLine)
0181 {
0182     if (m_editCurrentUndo != nullptr) { // do we care about notifications?
0183         addUndoItem(new KateModifiedWrapLine(m_document, line, col, length, newLine));
0184     }
0185 }
0186 
0187 void KateUndoManager::slotLineUnWrapped(int line, int col, int length, bool lineRemoved)
0188 {
0189     if (m_editCurrentUndo != nullptr) { // do we care about notifications?
0190         addUndoItem(new KateModifiedUnWrapLine(m_document, line, col, length, lineRemoved));
0191     }
0192 }
0193 
0194 void KateUndoManager::slotLineInserted(int line, const QString &s)
0195 {
0196     if (m_editCurrentUndo != nullptr) { // do we care about notifications?
0197         addUndoItem(new KateModifiedInsertLine(m_document, line, s));
0198     }
0199 }
0200 
0201 void KateUndoManager::slotLineRemoved(int line, const QString &s)
0202 {
0203     if (m_editCurrentUndo != nullptr) { // do we care about notifications?
0204         addUndoItem(new KateModifiedRemoveLine(m_document, line, s));
0205     }
0206 }
0207 
0208 void KateUndoManager::undoCancel()
0209 {
0210     // Don't worry about this when an edit is in progress
0211     if (m_document->isEditRunning()) {
0212         return;
0213     }
0214 
0215     undoSafePoint();
0216 }
0217 
0218 void KateUndoManager::undoSafePoint()
0219 {
0220     KateUndoGroup *undoGroup = m_editCurrentUndo;
0221 
0222     if (undoGroup == nullptr && !undoItems.isEmpty()) {
0223         undoGroup = undoItems.last();
0224     }
0225 
0226     if (undoGroup == nullptr) {
0227         return;
0228     }
0229 
0230     undoGroup->safePoint();
0231 }
0232 
0233 void KateUndoManager::addUndoItem(KateUndo *undo)
0234 {
0235     Q_ASSERT(undo != nullptr); // don't add null pointers to our history
0236     Q_ASSERT(m_editCurrentUndo != nullptr); // make sure there is an undo group for our item
0237 
0238     m_editCurrentUndo->addItem(undo);
0239 
0240     // Clear redo buffer
0241     qDeleteAll(redoItems);
0242     redoItems.clear();
0243 }
0244 
0245 void KateUndoManager::setActive(bool enabled)
0246 {
0247     Q_ASSERT(m_editCurrentUndo == nullptr); // must not already be in edit mode
0248     Q_ASSERT(m_isActive != enabled);
0249 
0250     m_isActive = enabled;
0251 
0252     Q_EMIT isActiveChanged(enabled);
0253 }
0254 
0255 uint KateUndoManager::undoCount() const
0256 {
0257     return undoItems.count();
0258 }
0259 
0260 uint KateUndoManager::redoCount() const
0261 {
0262     return redoItems.count();
0263 }
0264 
0265 void KateUndoManager::undo()
0266 {
0267     Q_ASSERT(m_editCurrentUndo == nullptr); // undo is not supported while we care about notifications (call editEnd() first)
0268 
0269     if (!undoItems.isEmpty()) {
0270         Q_EMIT undoStart(document());
0271 
0272         undoItems.last()->undo(activeView());
0273         redoItems.append(undoItems.last());
0274         undoItems.removeLast();
0275         updateModified();
0276 
0277         Q_EMIT undoEnd(document());
0278     }
0279 }
0280 
0281 void KateUndoManager::redo()
0282 {
0283     Q_ASSERT(m_editCurrentUndo == nullptr); // redo is not supported while we care about notifications (call editEnd() first)
0284 
0285     if (!redoItems.isEmpty()) {
0286         Q_EMIT redoStart(document());
0287 
0288         redoItems.last()->redo(activeView());
0289         undoItems.append(redoItems.last());
0290         redoItems.removeLast();
0291         updateModified();
0292 
0293         Q_EMIT redoEnd(document());
0294     }
0295 }
0296 
0297 void KateUndoManager::updateModified()
0298 {
0299     /*
0300     How this works:
0301 
0302       After noticing that there where to many scenarios to take into
0303       consideration when using 'if's to toggle the "Modified" flag
0304       I came up with this baby, flexible and repetitive calls are
0305       minimal.
0306 
0307       A numeric unique pattern is generated by toggling a set of bits,
0308       each bit symbolizes a different state in the Undo Redo structure.
0309 
0310         undoItems.isEmpty() != null          BIT 1
0311         redoItems.isEmpty() != null          BIT 2
0312         docWasSavedWhenUndoWasEmpty == true  BIT 3
0313         docWasSavedWhenRedoWasEmpty == true  BIT 4
0314         lastUndoGroupWhenSavedIsLastUndo     BIT 5
0315         lastUndoGroupWhenSavedIsLastRedo     BIT 6
0316         lastRedoGroupWhenSavedIsLastUndo     BIT 7
0317         lastRedoGroupWhenSavedIsLastRedo     BIT 8
0318 
0319       If you find a new pattern, please add it to the patterns array
0320     */
0321 
0322     unsigned char currentPattern = 0;
0323     const unsigned char patterns[] = {5, 16, 21, 24, 26, 88, 90, 93, 133, 144, 149, 154, 165};
0324     const unsigned char patternCount = sizeof(patterns);
0325     KateUndoGroup *undoLast = nullptr;
0326     KateUndoGroup *redoLast = nullptr;
0327 
0328     if (undoItems.isEmpty()) {
0329         currentPattern |= 1;
0330     } else {
0331         undoLast = undoItems.last();
0332     }
0333 
0334     if (redoItems.isEmpty()) {
0335         currentPattern |= 2;
0336     } else {
0337         redoLast = redoItems.last();
0338     }
0339 
0340     if (docWasSavedWhenUndoWasEmpty) {
0341         currentPattern |= 4;
0342     }
0343     if (docWasSavedWhenRedoWasEmpty) {
0344         currentPattern |= 8;
0345     }
0346     if (lastUndoGroupWhenSaved == undoLast) {
0347         currentPattern |= 16;
0348     }
0349     if (lastUndoGroupWhenSaved == redoLast) {
0350         currentPattern |= 32;
0351     }
0352     if (lastRedoGroupWhenSaved == undoLast) {
0353         currentPattern |= 64;
0354     }
0355     if (lastRedoGroupWhenSaved == redoLast) {
0356         currentPattern |= 128;
0357     }
0358 
0359     // This will print out the pattern information
0360 
0361     qCDebug(LOG_KTE) << "Pattern:" << static_cast<unsigned int>(currentPattern);
0362 
0363     for (uint patternIndex = 0; patternIndex < patternCount; ++patternIndex) {
0364         if (currentPattern == patterns[patternIndex]) {
0365             // Note: m_document->setModified() calls KateUndoManager::setModified!
0366             m_document->setModified(false);
0367             // (dominik) whenever the doc is not modified, succeeding edits
0368             // should not be merged
0369             undoSafePoint();
0370             qCDebug(LOG_KTE) << "setting modified to false!";
0371             break;
0372         }
0373     }
0374 }
0375 
0376 void KateUndoManager::clearUndo()
0377 {
0378     qDeleteAll(undoItems);
0379     undoItems.clear();
0380 
0381     lastUndoGroupWhenSaved = nullptr;
0382     docWasSavedWhenUndoWasEmpty = false;
0383 
0384     Q_EMIT undoChanged();
0385 }
0386 
0387 void KateUndoManager::clearRedo()
0388 {
0389     qDeleteAll(redoItems);
0390     redoItems.clear();
0391 
0392     lastRedoGroupWhenSaved = nullptr;
0393     docWasSavedWhenRedoWasEmpty = false;
0394 
0395     Q_EMIT undoChanged();
0396 }
0397 
0398 void KateUndoManager::setModified(bool modified)
0399 {
0400     if (!modified) {
0401         if (!undoItems.isEmpty()) {
0402             lastUndoGroupWhenSaved = undoItems.last();
0403         }
0404 
0405         if (!redoItems.isEmpty()) {
0406             lastRedoGroupWhenSaved = redoItems.last();
0407         }
0408 
0409         docWasSavedWhenUndoWasEmpty = undoItems.isEmpty();
0410         docWasSavedWhenRedoWasEmpty = redoItems.isEmpty();
0411     }
0412 }
0413 
0414 void KateUndoManager::updateLineModifications()
0415 {
0416     // change LineSaved flag of all undo & redo items to LineModified
0417     for (KateUndoGroup *undoGroup : std::as_const(undoItems)) {
0418         undoGroup->flagSavedAsModified();
0419     }
0420 
0421     for (KateUndoGroup *undoGroup : std::as_const(redoItems)) {
0422         undoGroup->flagSavedAsModified();
0423     }
0424 
0425     // iterate all undo/redo items to find out, which item sets the flag LineSaved
0426     QBitArray lines(document()->lines(), false);
0427     for (int i = undoItems.size() - 1; i >= 0; --i) {
0428         undoItems[i]->markRedoAsSaved(lines);
0429     }
0430 
0431     lines.fill(false);
0432     for (int i = redoItems.size() - 1; i >= 0; --i) {
0433         redoItems[i]->markUndoAsSaved(lines);
0434     }
0435 }
0436 
0437 void KateUndoManager::setUndoRedoCursorsOfLastGroup(const KTextEditor::Cursor undoCursor, const KTextEditor::Cursor redoCursor)
0438 {
0439     Q_ASSERT(m_editCurrentUndo == nullptr);
0440     if (!undoItems.isEmpty()) {
0441         KateUndoGroup *last = undoItems.last();
0442         last->setUndoCursor(undoCursor);
0443         last->setRedoCursor(redoCursor);
0444     }
0445 }
0446 
0447 KTextEditor::Cursor KateUndoManager::lastRedoCursor() const
0448 {
0449     Q_ASSERT(m_editCurrentUndo == nullptr);
0450     if (!undoItems.isEmpty()) {
0451         KateUndoGroup *last = undoItems.last();
0452         return last->redoCursor();
0453     }
0454     return KTextEditor::Cursor::invalid();
0455 }
0456 
0457 void KateUndoManager::updateConfig()
0458 {
0459     Q_EMIT undoChanged();
0460 }
0461 
0462 void KateUndoManager::setAllowComplexMerge(bool allow)
0463 {
0464     m_undoComplexMerge = allow;
0465 }
0466 
0467 KTextEditor::ViewPrivate *KateUndoManager::activeView()
0468 {
0469     return static_cast<KTextEditor::ViewPrivate *>(m_document->activeView());
0470 }
0471 
0472 #include "moc_kateundomanager.cpp"