File indexing completed on 2024-04-28 15:30:50
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"