File indexing completed on 2024-04-14 03:55:27

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 "katepartdebug.h"
0013 #include "kateview.h"
0014 
0015 #include <QBitArray>
0016 
0017 KateUndoManager::KateUndoManager(KTextEditor::DocumentPrivate *doc)
0018     : QObject(doc)
0019     , m_document(doc)
0020 {
0021     connect(this, &KateUndoManager::undoEnd, this, &KateUndoManager::undoChanged);
0022     connect(this, &KateUndoManager::redoEnd, this, &KateUndoManager::undoChanged);
0023 
0024     connect(doc, &KTextEditor::DocumentPrivate::viewCreated, this, &KateUndoManager::viewCreated);
0025 
0026     // Before reload save history
0027     connect(doc, &KTextEditor::DocumentPrivate::aboutToReload, this, [this] {
0028         savedUndoItems = std::move(undoItems);
0029         savedRedoItems = std::move(redoItems);
0030         docChecksumBeforeReload = m_document->checksum();
0031     });
0032 
0033     // After reload restore it only if checksum of the doc is same
0034     connect(doc, &KTextEditor::DocumentPrivate::loaded, this, [this](KTextEditor::Document *doc) {
0035         if (doc && !doc->checksum().isEmpty() && !docChecksumBeforeReload.isEmpty() && doc->checksum() == docChecksumBeforeReload) {
0036             undoItems = std::move(savedUndoItems);
0037             redoItems = std::move(savedRedoItems);
0038             Q_EMIT undoChanged();
0039         }
0040         docChecksumBeforeReload.clear();
0041         savedUndoItems.clear();
0042         savedRedoItems.clear();
0043     });
0044 }
0045 
0046 KateUndoManager::~KateUndoManager() = default;
0047 
0048 void KateUndoManager::viewCreated(KTextEditor::Document *, KTextEditor::View *newView) const
0049 {
0050     connect(newView, &KTextEditor::View::cursorPositionChanged, this, &KateUndoManager::undoCancel);
0051 }
0052 
0053 void KateUndoManager::editStart()
0054 {
0055     if (!m_isActive) {
0056         return;
0057     }
0058 
0059     // editStart() and editEnd() must be called in alternating fashion
0060     Q_ASSERT(!m_editCurrentUndo.has_value()); // make sure to enter a clean state
0061 
0062     const KTextEditor::Cursor cursorPosition = activeView() ? activeView()->cursorPosition() : KTextEditor::Cursor::invalid();
0063     const KTextEditor::Range primarySelectionRange = activeView() ? activeView()->selectionRange() : KTextEditor::Range::invalid();
0064     QList<KTextEditor::ViewPrivate::PlainSecondaryCursor> secondaryCursors;
0065     if (activeView()) {
0066         secondaryCursors = activeView()->plainSecondaryCursors();
0067     }
0068 
0069     // new current undo item
0070     m_editCurrentUndo = KateUndoGroup(cursorPosition, primarySelectionRange, secondaryCursors);
0071 
0072     Q_ASSERT(m_editCurrentUndo.has_value()); // a new undo group must be created by this method
0073 }
0074 
0075 void KateUndoManager::editEnd()
0076 {
0077     if (!m_isActive) {
0078         return;
0079     }
0080 
0081     // editStart() and editEnd() must be called in alternating fashion
0082     Q_ASSERT(m_editCurrentUndo.has_value()); // an undo group must have been created by editStart()
0083 
0084     const KTextEditor::Cursor cursorPosition = activeView() ? activeView()->cursorPosition() : KTextEditor::Cursor::invalid();
0085     const KTextEditor::Range selectionRange = activeView() ? activeView()->selectionRange() : KTextEditor::Range::invalid();
0086 
0087     QList<KTextEditor::ViewPrivate::PlainSecondaryCursor> secondaryCursors;
0088     if (activeView()) {
0089         secondaryCursors = activeView()->plainSecondaryCursors();
0090     }
0091 
0092     m_editCurrentUndo->editEnd(cursorPosition, selectionRange, secondaryCursors);
0093 
0094     bool changedUndo = false;
0095 
0096     if (m_editCurrentUndo->isEmpty()) {
0097         m_editCurrentUndo.reset();
0098     } else if (!undoItems.empty() && undoItems.back().merge(&*m_editCurrentUndo, m_undoComplexMerge)) {
0099         m_editCurrentUndo.reset();
0100     } else {
0101         undoItems.push_back(std::move(*m_editCurrentUndo));
0102         changedUndo = true;
0103     }
0104 
0105     m_editCurrentUndo.reset();
0106 
0107     if (changedUndo) {
0108         Q_EMIT undoChanged();
0109     }
0110 
0111     Q_ASSERT(!m_editCurrentUndo.has_value()); // must be 0 after calling this method
0112 }
0113 
0114 void KateUndoManager::inputMethodStart()
0115 {
0116     setActive(false);
0117     m_document->editStart();
0118 }
0119 
0120 void KateUndoManager::inputMethodEnd()
0121 {
0122     m_document->editEnd();
0123     setActive(true);
0124 }
0125 
0126 void KateUndoManager::startUndo()
0127 {
0128     setActive(false);
0129     m_document->editStart();
0130 }
0131 
0132 void KateUndoManager::endUndo()
0133 {
0134     m_document->editEnd();
0135     setActive(true);
0136 }
0137 
0138 void KateUndoManager::slotTextInserted(int line, int col, const QString &s, const Kate::TextLine &tl)
0139 {
0140     if (!m_editCurrentUndo.has_value() || s.isEmpty()) { // do we care about notifications?
0141         return;
0142     }
0143 
0144     UndoItem item;
0145     item.type = UndoItem::editInsertText;
0146     item.line = line;
0147     item.col = col;
0148     item.text = s;
0149     item.lineModFlags.setFlag(UndoItem::RedoLine1Modified);
0150 
0151     if (tl.markedAsModified()) {
0152         item.lineModFlags.setFlag(UndoItem::UndoLine1Modified);
0153     } else {
0154         item.lineModFlags.setFlag(UndoItem::UndoLine1Saved);
0155     }
0156     addUndoItem(std::move(item));
0157 }
0158 
0159 void KateUndoManager::slotTextRemoved(int line, int col, const QString &s, const Kate::TextLine &tl)
0160 {
0161     if (!m_editCurrentUndo.has_value() || s.isEmpty()) { // do we care about notifications?
0162         return;
0163     }
0164 
0165     UndoItem item;
0166     item.type = UndoItem::editRemoveText;
0167     item.line = line;
0168     item.col = col;
0169     item.text = s;
0170     item.lineModFlags.setFlag(UndoItem::RedoLine1Modified);
0171 
0172     if (tl.markedAsModified()) {
0173         item.lineModFlags.setFlag(UndoItem::UndoLine1Modified);
0174     } else {
0175         item.lineModFlags.setFlag(UndoItem::UndoLine1Saved);
0176     }
0177     addUndoItem(std::move(item));
0178 }
0179 
0180 void KateUndoManager::slotMarkLineAutoWrapped(int line, bool autowrapped)
0181 {
0182     if (m_editCurrentUndo.has_value()) { // do we care about notifications?
0183         UndoItem item;
0184         item.type = UndoItem::editMarkLineAutoWrapped;
0185         item.line = line;
0186         item.autowrapped = autowrapped;
0187         addUndoItem(std::move(item));
0188     }
0189 }
0190 
0191 void KateUndoManager::slotLineWrapped(int line, int col, int length, bool newLine, const Kate::TextLine &tl)
0192 {
0193     if (!m_editCurrentUndo.has_value()) { // do we care about notifications?
0194         return;
0195     }
0196 
0197     UndoItem item;
0198     item.type = UndoItem::editWrapLine;
0199     item.line = line;
0200     item.col = col;
0201     item.len = length;
0202     item.newLine = newLine;
0203 
0204     if (length > 0 || tl.markedAsModified()) {
0205         item.lineModFlags.setFlag(UndoItem::RedoLine1Modified);
0206     } else if (tl.markedAsSavedOnDisk()) {
0207         item.lineModFlags.setFlag(UndoItem::RedoLine1Saved);
0208     }
0209 
0210     if (col > 0 || length == 0 || tl.markedAsModified()) {
0211         item.lineModFlags.setFlag(UndoItem::RedoLine2Modified);
0212     } else if (tl.markedAsSavedOnDisk()) {
0213         item.lineModFlags.setFlag(UndoItem::RedoLine2Saved);
0214     }
0215 
0216     if (tl.markedAsModified()) {
0217         item.lineModFlags.setFlag(UndoItem::UndoLine1Modified);
0218     } else if ((length > 0 && col > 0) || tl.markedAsSavedOnDisk()) {
0219         item.lineModFlags.setFlag(UndoItem::UndoLine1Saved);
0220     }
0221 
0222     addUndoItem(std::move(item));
0223 }
0224 
0225 void KateUndoManager::slotLineUnWrapped(int line, int col, int length, bool lineRemoved, const Kate::TextLine &tl, const Kate::TextLine &nextLine)
0226 {
0227     if (!m_editCurrentUndo.has_value()) { // do we care about notifications?
0228         return;
0229     }
0230 
0231     UndoItem item;
0232     item.type = UndoItem::editUnWrapLine;
0233     item.line = line;
0234     item.col = col;
0235     item.len = length;
0236     item.removeLine = lineRemoved;
0237 
0238     const int len1 = tl.length();
0239     const int len2 = nextLine.length();
0240 
0241     if (len1 > 0 && len2 > 0) {
0242         item.lineModFlags.setFlag(UndoItem::RedoLine1Modified);
0243 
0244         if (tl.markedAsModified()) {
0245             item.lineModFlags.setFlag(UndoItem::UndoLine1Modified);
0246         } else {
0247             item.lineModFlags.setFlag(UndoItem::UndoLine1Saved);
0248         }
0249 
0250         if (nextLine.markedAsModified()) {
0251             item.lineModFlags.setFlag(UndoItem::UndoLine2Modified);
0252         } else {
0253             item.lineModFlags.setFlag(UndoItem::UndoLine2Saved);
0254         }
0255     } else if (len1 == 0) {
0256         if (nextLine.markedAsModified()) {
0257             item.lineModFlags.setFlag(UndoItem::RedoLine1Modified);
0258         } else if (nextLine.markedAsSavedOnDisk()) {
0259             item.lineModFlags.setFlag(UndoItem::RedoLine1Saved);
0260         }
0261 
0262         if (tl.markedAsModified()) {
0263             item.lineModFlags.setFlag(UndoItem::UndoLine1Modified);
0264         } else {
0265             item.lineModFlags.setFlag(UndoItem::UndoLine1Saved);
0266         }
0267 
0268         if (nextLine.markedAsModified()) {
0269             item.lineModFlags.setFlag(UndoItem::UndoLine2Modified);
0270         } else if (nextLine.markedAsSavedOnDisk()) {
0271             item.lineModFlags.setFlag(UndoItem::UndoLine2Saved);
0272         }
0273     } else { // len2 == 0
0274         if (nextLine.markedAsModified()) {
0275             item.lineModFlags.setFlag(UndoItem::RedoLine1Modified);
0276         } else if (nextLine.markedAsSavedOnDisk()) {
0277             item.lineModFlags.setFlag(UndoItem::RedoLine1Saved);
0278         }
0279 
0280         if (tl.markedAsModified()) {
0281             item.lineModFlags.setFlag(UndoItem::UndoLine1Modified);
0282         } else if (tl.markedAsSavedOnDisk()) {
0283             item.lineModFlags.setFlag(UndoItem::UndoLine1Saved);
0284         }
0285 
0286         if (nextLine.markedAsModified()) {
0287             item.lineModFlags.setFlag(UndoItem::UndoLine2Modified);
0288         } else {
0289             item.lineModFlags.setFlag(UndoItem::UndoLine2Saved);
0290         }
0291     }
0292 
0293     addUndoItem(std::move(item));
0294 }
0295 
0296 void KateUndoManager::slotLineInserted(int line, const QString &s)
0297 {
0298     if (m_editCurrentUndo.has_value()) { // do we care about notifications?
0299         UndoItem item;
0300         item.type = UndoItem::editInsertLine;
0301         item.line = line;
0302         item.text = s;
0303         item.lineModFlags.setFlag(UndoItem::RedoLine1Modified);
0304         addUndoItem(std::move(item));
0305     }
0306 }
0307 
0308 void KateUndoManager::slotLineRemoved(int line, const QString &s, const Kate::TextLine &tl)
0309 {
0310     if (m_editCurrentUndo.has_value()) { // do we care about notifications?
0311         UndoItem item;
0312         item.type = UndoItem::editRemoveLine;
0313         item.line = line;
0314         item.text = s;
0315         item.lineModFlags.setFlag(UndoItem::RedoLine1Modified);
0316 
0317         if (tl.markedAsModified()) {
0318             item.lineModFlags.setFlag(UndoItem::UndoLine1Modified);
0319         } else {
0320             item.lineModFlags.setFlag(UndoItem::UndoLine1Saved);
0321         }
0322         addUndoItem(std::move(item));
0323     }
0324 }
0325 
0326 void KateUndoManager::undoCancel()
0327 {
0328     // Don't worry about this when an edit is in progress
0329     if (m_document->isEditRunning()) {
0330         return;
0331     }
0332 
0333     undoSafePoint();
0334 }
0335 
0336 void KateUndoManager::undoSafePoint()
0337 {
0338     if (!m_editCurrentUndo.has_value() && !undoItems.empty()) {
0339         undoItems.back().safePoint();
0340     } else if (m_editCurrentUndo.has_value()) {
0341         m_editCurrentUndo.value().safePoint();
0342     }
0343 }
0344 
0345 void KateUndoManager::addUndoItem(UndoItem undo)
0346 {
0347     Q_ASSERT(m_editCurrentUndo.has_value()); // make sure there is an undo group for our item
0348 
0349     m_editCurrentUndo->addItem(std::move(undo));
0350 
0351     // Clear redo buffer
0352     redoItems.clear();
0353 }
0354 
0355 void KateUndoManager::setActive(bool enabled)
0356 {
0357     Q_ASSERT(!m_editCurrentUndo.has_value()); // must not already be in edit mode
0358     Q_ASSERT(m_isActive != enabled);
0359 
0360     m_isActive = enabled;
0361 
0362     Q_EMIT isActiveChanged(enabled);
0363 }
0364 
0365 uint KateUndoManager::undoCount() const
0366 {
0367     return undoItems.size();
0368 }
0369 
0370 uint KateUndoManager::redoCount() const
0371 {
0372     return redoItems.size();
0373 }
0374 
0375 void KateUndoManager::undo()
0376 {
0377     Q_ASSERT(!m_editCurrentUndo.has_value()); // undo is not supported while we care about notifications (call editEnd() first)
0378 
0379     if (!undoItems.empty()) {
0380         Q_EMIT undoStart(document());
0381 
0382         undoItems.back().undo(this, activeView());
0383         redoItems.push_back(std::move(undoItems.back()));
0384         undoItems.pop_back();
0385         updateModified();
0386 
0387         Q_EMIT undoEnd(document());
0388     }
0389 }
0390 
0391 void KateUndoManager::redo()
0392 {
0393     Q_ASSERT(!m_editCurrentUndo.has_value()); // redo is not supported while we care about notifications (call editEnd() first)
0394 
0395     if (!redoItems.empty()) {
0396         Q_EMIT redoStart(document());
0397 
0398         redoItems.back().redo(this, activeView());
0399         undoItems.push_back(std::move(redoItems.back()));
0400         redoItems.pop_back();
0401         updateModified();
0402 
0403         Q_EMIT redoEnd(document());
0404     }
0405 }
0406 
0407 void KateUndoManager::updateModified()
0408 {
0409     /*
0410     How this works:
0411 
0412       After noticing that there where to many scenarios to take into
0413       consideration when using 'if's to toggle the "Modified" flag
0414       I came up with this baby, flexible and repetitive calls are
0415       minimal.
0416 
0417       A numeric unique pattern is generated by toggling a set of bits,
0418       each bit symbolizes a different state in the Undo Redo structure.
0419 
0420         undoItems.isEmpty() != null          BIT 1
0421         redoItems.isEmpty() != null          BIT 2
0422         docWasSavedWhenUndoWasEmpty == true  BIT 3
0423         docWasSavedWhenRedoWasEmpty == true  BIT 4
0424         lastUndoGroupWhenSavedIsLastUndo     BIT 5
0425         lastUndoGroupWhenSavedIsLastRedo     BIT 6
0426         lastRedoGroupWhenSavedIsLastUndo     BIT 7
0427         lastRedoGroupWhenSavedIsLastRedo     BIT 8
0428 
0429       If you find a new pattern, please add it to the patterns array
0430     */
0431 
0432     unsigned char currentPattern = 0;
0433     const unsigned char patterns[] = {5, 16, 21, 24, 26, 88, 90, 93, 133, 144, 149, 154, 165};
0434     const unsigned char patternCount = sizeof(patterns);
0435     KateUndoGroup *undoLast = nullptr;
0436     KateUndoGroup *redoLast = nullptr;
0437 
0438     if (undoItems.empty()) {
0439         currentPattern |= 1;
0440     } else {
0441         undoLast = &undoItems.back();
0442     }
0443 
0444     if (redoItems.empty()) {
0445         currentPattern |= 2;
0446     } else {
0447         redoLast = &redoItems.back();
0448     }
0449 
0450     if (docWasSavedWhenUndoWasEmpty) {
0451         currentPattern |= 4;
0452     }
0453     if (docWasSavedWhenRedoWasEmpty) {
0454         currentPattern |= 8;
0455     }
0456     if (lastUndoGroupWhenSaved == undoLast) {
0457         currentPattern |= 16;
0458     }
0459     if (lastUndoGroupWhenSaved == redoLast) {
0460         currentPattern |= 32;
0461     }
0462     if (lastRedoGroupWhenSaved == undoLast) {
0463         currentPattern |= 64;
0464     }
0465     if (lastRedoGroupWhenSaved == redoLast) {
0466         currentPattern |= 128;
0467     }
0468 
0469     // This will print out the pattern information
0470 
0471     qCDebug(LOG_KTE) << "Pattern:" << static_cast<unsigned int>(currentPattern);
0472 
0473     for (uint patternIndex = 0; patternIndex < patternCount; ++patternIndex) {
0474         if (currentPattern == patterns[patternIndex]) {
0475             // Note: m_document->setModified() calls KateUndoManager::setModified!
0476             m_document->setModified(false);
0477             // (dominik) whenever the doc is not modified, succeeding edits
0478             // should not be merged
0479             undoSafePoint();
0480             qCDebug(LOG_KTE) << "setting modified to false!";
0481             break;
0482         }
0483     }
0484 }
0485 
0486 void KateUndoManager::clearUndo()
0487 {
0488     undoItems.clear();
0489 
0490     lastUndoGroupWhenSaved = nullptr;
0491     docWasSavedWhenUndoWasEmpty = false;
0492 
0493     Q_EMIT undoChanged();
0494 }
0495 
0496 void KateUndoManager::clearRedo()
0497 {
0498     redoItems.clear();
0499 
0500     lastRedoGroupWhenSaved = nullptr;
0501     docWasSavedWhenRedoWasEmpty = false;
0502 
0503     Q_EMIT undoChanged();
0504 }
0505 
0506 void KateUndoManager::setModified(bool modified)
0507 {
0508     if (!modified) {
0509         if (!undoItems.empty()) {
0510             lastUndoGroupWhenSaved = &undoItems.back();
0511         }
0512 
0513         if (!redoItems.empty()) {
0514             lastRedoGroupWhenSaved = &redoItems.back();
0515         }
0516 
0517         docWasSavedWhenUndoWasEmpty = undoItems.empty();
0518         docWasSavedWhenRedoWasEmpty = redoItems.empty();
0519     }
0520 }
0521 
0522 void KateUndoManager::updateLineModifications()
0523 {
0524     // change LineSaved flag of all undo & redo items to LineModified
0525     for (KateUndoGroup &undoGroup : undoItems) {
0526         undoGroup.flagSavedAsModified();
0527     }
0528 
0529     for (KateUndoGroup &undoGroup : redoItems) {
0530         undoGroup.flagSavedAsModified();
0531     }
0532 
0533     // iterate all undo/redo items to find out, which item sets the flag LineSaved
0534     QBitArray lines(document()->lines(), false);
0535     for (int i = undoItems.size() - 1; i >= 0; --i) {
0536         undoItems[i].markRedoAsSaved(lines);
0537     }
0538 
0539     lines.fill(false);
0540     for (int i = redoItems.size() - 1; i >= 0; --i) {
0541         redoItems[i].markUndoAsSaved(lines);
0542     }
0543 }
0544 
0545 void KateUndoManager::setUndoRedoCursorsOfLastGroup(const KTextEditor::Cursor undoCursor, const KTextEditor::Cursor redoCursor)
0546 {
0547     Q_ASSERT(!m_editCurrentUndo.has_value());
0548     if (!undoItems.empty()) {
0549         KateUndoGroup &last = undoItems.back();
0550         last.setUndoCursor(undoCursor);
0551         last.setRedoCursor(redoCursor);
0552     }
0553 }
0554 
0555 KTextEditor::Cursor KateUndoManager::lastRedoCursor() const
0556 {
0557     Q_ASSERT(!m_editCurrentUndo.has_value());
0558     if (!undoItems.empty()) {
0559         undoItems.back().redoCursor();
0560     }
0561     return KTextEditor::Cursor::invalid();
0562 }
0563 
0564 void KateUndoManager::updateConfig()
0565 {
0566     Q_EMIT undoChanged();
0567 }
0568 
0569 void KateUndoManager::setAllowComplexMerge(bool allow)
0570 {
0571     m_undoComplexMerge = allow;
0572 }
0573 
0574 KTextEditor::ViewPrivate *KateUndoManager::activeView()
0575 {
0576     return static_cast<KTextEditor::ViewPrivate *>(m_document->activeView());
0577 }
0578 
0579 #include "moc_kateundomanager.cpp"