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"