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

0001 /*
0002     SPDX-FileCopyrightText: 2011 Dominik Haumann <dhaumann@kde.org>
0003     SPDX-FileCopyrightText: 2009-2010 Bernhard Beschow <bbeschow@cs.tu-berlin.de>
0004     SPDX-FileCopyrightText: 2002 John Firebaugh <jfirebaugh@kde.org>
0005     SPDX-FileCopyrightText: 2001 Christoph Cullmann <cullmann@kde.org>
0006     SPDX-FileCopyrightText: 2001 Joseph Wenninger <jowenn@kde.org>
0007     SPDX-FileCopyrightText: 2023 Waqar Ahmed <waqar.17a@gmail.com>
0008 
0009     SPDX-License-Identifier: LGPL-2.0-or-later
0010 */
0011 
0012 #include "kateundo.h"
0013 
0014 #include "katebuffer.h"
0015 #include "katedocument.h"
0016 #include "kateundomanager.h"
0017 #include "kateview.h"
0018 
0019 #include <ktexteditor/cursor.h>
0020 #include <ktexteditor/view.h>
0021 
0022 KateUndoGroup::KateUndoGroup(const KTextEditor::Cursor cursorPosition,
0023                              KTextEditor::Range selection,
0024                              const QList<KTextEditor::ViewPrivate::PlainSecondaryCursor> &secondary)
0025     : m_undoSelection(selection)
0026     , m_redoSelection(-1, -1, -1, -1)
0027     , m_undoCursor(cursorPosition)
0028     , m_undoSecondaryCursors(secondary)
0029     , m_redoCursor(-1, -1)
0030 {
0031 }
0032 
0033 void KateUndoGroup::undo(KateUndoManager *manager, KTextEditor::ViewPrivate *view)
0034 {
0035     if (m_items.empty()) {
0036         return;
0037     }
0038 
0039     manager->startUndo();
0040 
0041     auto doc = manager->document();
0042     auto updateDocLine = [doc](const UndoItem &item) {
0043         Kate::TextLine tl = doc->plainKateTextLine(item.line);
0044         tl.markAsModified(item.lineModFlags.testFlag(UndoItem::UndoLine1Modified));
0045         tl.markAsSavedOnDisk(item.lineModFlags.testFlag(UndoItem::UndoLine1Saved));
0046         doc->buffer().setLineMetaData(item.line, tl);
0047     };
0048 
0049     for (auto rit = m_items.rbegin(); rit != m_items.rend(); ++rit) {
0050         auto &item = *rit;
0051         switch (item.type) {
0052         case UndoItem::editInsertText:
0053             doc->editRemoveText(item.line, item.col, item.text.size());
0054             updateDocLine(item);
0055             break;
0056         case UndoItem::editRemoveText:
0057             doc->editInsertText(item.line, item.col, item.text);
0058             updateDocLine(item);
0059             break;
0060         case UndoItem::editWrapLine:
0061             doc->editUnWrapLine(item.line, item.newLine, item.len);
0062             updateDocLine(item);
0063             break;
0064         case UndoItem::editUnWrapLine: {
0065             doc->editWrapLine(item.line, item.col, item.removeLine);
0066             updateDocLine(item);
0067 
0068             auto next = doc->plainKateTextLine(item.line + 1);
0069             next.markAsModified(item.lineModFlags.testFlag(UndoItem::UndoLine2Modified));
0070             next.markAsSavedOnDisk(item.lineModFlags.testFlag(UndoItem::UndoLine2Saved));
0071             doc->buffer().setLineMetaData(item.line + 1, next);
0072         } break;
0073         case UndoItem::editInsertLine:
0074             doc->editRemoveLine(item.line);
0075             break;
0076         case UndoItem::editRemoveLine:
0077             doc->editInsertLine(item.line, item.text);
0078             updateDocLine(item);
0079             break;
0080         case UndoItem::editMarkLineAutoWrapped:
0081             doc->editMarkLineAutoWrapped(item.line, item.autowrapped);
0082             break;
0083         case UndoItem::editInvalid:
0084             break;
0085         }
0086     }
0087 
0088     if (view != nullptr) {
0089         if (m_undoSelection.isValid()) {
0090             view->setSelection(m_undoSelection);
0091         } else {
0092             view->removeSelection();
0093         }
0094         view->clearSecondaryCursors();
0095         view->addSecondaryCursorsWithSelection(m_undoSecondaryCursors);
0096 
0097         if (m_undoCursor.isValid()) {
0098             view->setCursorPosition(m_undoCursor);
0099         }
0100     }
0101 
0102     manager->endUndo();
0103 }
0104 
0105 void KateUndoGroup::redo(KateUndoManager *manager, KTextEditor::ViewPrivate *view)
0106 {
0107     if (m_items.empty()) {
0108         return;
0109     }
0110 
0111     manager->startUndo();
0112 
0113     auto doc = manager->document();
0114     auto updateDocLine = [doc](const UndoItem &item) {
0115         Kate::TextLine tl = doc->plainKateTextLine(item.line);
0116         tl.markAsModified(item.lineModFlags.testFlag(UndoItem::RedoLine1Modified));
0117         tl.markAsSavedOnDisk(item.lineModFlags.testFlag(UndoItem::RedoLine1Saved));
0118         doc->buffer().setLineMetaData(item.line, tl);
0119     };
0120 
0121     for (auto &item : m_items) {
0122         switch (item.type) {
0123         case UndoItem::editInsertText:
0124             doc->editInsertText(item.line, item.col, item.text);
0125             updateDocLine(item);
0126             break;
0127         case UndoItem::editRemoveText:
0128             doc->editRemoveText(item.line, item.col, item.text.size());
0129             updateDocLine(item);
0130             break;
0131         case UndoItem::editWrapLine: {
0132             doc->editWrapLine(item.line, item.col, item.newLine);
0133             updateDocLine(item);
0134 
0135             Kate::TextLine next = doc->plainKateTextLine(item.line + 1);
0136             next.markAsModified(item.lineModFlags.testFlag(UndoItem::RedoLine2Modified));
0137             next.markAsSavedOnDisk(item.lineModFlags.testFlag(UndoItem::RedoLine2Saved));
0138             doc->buffer().setLineMetaData(item.line + 1, next);
0139         } break;
0140         case UndoItem::editUnWrapLine:
0141             doc->editUnWrapLine(item.line, item.removeLine, item.len);
0142             updateDocLine(item);
0143             break;
0144         case UndoItem::editInsertLine:
0145             doc->editInsertLine(item.line, item.text);
0146             updateDocLine(item);
0147             break;
0148         case UndoItem::editRemoveLine:
0149             doc->editRemoveLine(item.line);
0150             break;
0151         case UndoItem::editMarkLineAutoWrapped:
0152             doc->editMarkLineAutoWrapped(item.line, item.autowrapped);
0153             break;
0154         case UndoItem::editInvalid:
0155             break;
0156         }
0157     }
0158 
0159     if (view != nullptr) {
0160         if (m_redoSelection.isValid()) {
0161             view->setSelection(m_redoSelection);
0162         } else {
0163             view->removeSelection();
0164         }
0165         view->clearSecondaryCursors();
0166         view->addSecondaryCursorsWithSelection(m_redoSecondaryCursors);
0167 
0168         if (m_redoCursor.isValid()) {
0169             view->setCursorPosition(m_redoCursor);
0170         }
0171     }
0172 
0173     manager->endUndo();
0174 }
0175 
0176 void KateUndoGroup::editEnd(const KTextEditor::Cursor cursorPosition,
0177                             KTextEditor::Range selectionRange,
0178                             const QList<KTextEditor::ViewPrivate::PlainSecondaryCursor> &secondaryCursors)
0179 {
0180     m_redoCursor = cursorPosition;
0181     m_redoSecondaryCursors = secondaryCursors;
0182     m_redoSelection = selectionRange;
0183 }
0184 
0185 static bool mergeUndoItems(UndoItem &base, const UndoItem &u)
0186 {
0187     if (base.type == UndoItem::editInsertText && u.type == UndoItem::editWrapLine) {
0188         // merge insert text full line + wrap line
0189         if (base.col == 0 && base.line == u.line && base.col + base.text.length() == u.col && u.newLine) {
0190             base.type = UndoItem::editInsertLine;
0191             base.lineModFlags.setFlag(UndoItem::RedoLine1Modified);
0192             return true;
0193         }
0194     }
0195 
0196     if (base.type == UndoItem::editRemoveText && base.type == u.type) {
0197         if (base.line == u.line && base.col == (u.col + u.text.size())) {
0198             base.text.prepend(u.text);
0199             base.col = u.col;
0200             return true;
0201         }
0202     }
0203 
0204     if (base.type == UndoItem::editInsertText && base.type == u.type) {
0205         if (base.line == u.line && (base.col + base.text.size()) == u.col) {
0206             base.text += u.text;
0207             return true;
0208         }
0209     }
0210 
0211     return false;
0212 }
0213 
0214 void KateUndoGroup::addItem(UndoItem u)
0215 {
0216     // try to merge, do that only for equal types, inside mergeWith we do hard casts
0217     if (!m_items.empty() && mergeUndoItems(m_items.back(), u)) {
0218         return;
0219     }
0220 
0221     // default: just add new item unchanged
0222     m_items.push_back(std::move(u));
0223 }
0224 
0225 bool KateUndoGroup::merge(KateUndoGroup *newGroup, bool complex)
0226 {
0227     if (m_safePoint) {
0228         return false;
0229     }
0230 
0231     if (newGroup->isOnlyType(singleType()) || complex) {
0232         // Take all of its items first -> last
0233         for (auto &item : newGroup->m_items) {
0234             addItem(item);
0235         }
0236         newGroup->m_items.clear();
0237 
0238         if (newGroup->m_safePoint) {
0239             safePoint();
0240         }
0241 
0242         m_redoCursor = newGroup->m_redoCursor;
0243         m_redoSecondaryCursors = newGroup->m_redoSecondaryCursors;
0244         m_redoSelection = newGroup->m_redoSelection;
0245         //         m_redoSelections = newGroup->m_redoSelections;
0246 
0247         return true;
0248     }
0249 
0250     return false;
0251 }
0252 
0253 void KateUndoGroup::safePoint(bool safePoint)
0254 {
0255     m_safePoint = safePoint;
0256 }
0257 
0258 void KateUndoGroup::flagSavedAsModified()
0259 {
0260     for (UndoItem &item : m_items) {
0261         if (item.lineModFlags.testFlag(UndoItem::UndoLine1Saved)) {
0262             item.lineModFlags.setFlag(UndoItem::UndoLine1Saved, false);
0263             item.lineModFlags.setFlag(UndoItem::UndoLine1Modified, true);
0264         }
0265 
0266         if (item.lineModFlags.testFlag(UndoItem::UndoLine2Saved)) {
0267             item.lineModFlags.setFlag(UndoItem::UndoLine2Saved, false);
0268             item.lineModFlags.setFlag(UndoItem::UndoLine2Modified, true);
0269         }
0270 
0271         if (item.lineModFlags.testFlag(UndoItem::RedoLine1Saved)) {
0272             item.lineModFlags.setFlag(UndoItem::RedoLine1Saved, false);
0273             item.lineModFlags.setFlag(UndoItem::RedoLine1Modified, true);
0274         }
0275 
0276         if (item.lineModFlags.testFlag(UndoItem::RedoLine2Saved)) {
0277             item.lineModFlags.setFlag(UndoItem::RedoLine2Saved, false);
0278             item.lineModFlags.setFlag(UndoItem::RedoLine2Modified, true);
0279         }
0280     }
0281 }
0282 
0283 static void updateUndoSavedOnDiskFlag(UndoItem &item, QBitArray &lines)
0284 {
0285     const int line = item.line;
0286     if (line >= lines.size()) {
0287         lines.resize(line + 1);
0288     }
0289 
0290     const bool wasBitSet = lines.testBit(line);
0291     if (!wasBitSet) {
0292         lines.setBit(line);
0293     }
0294 
0295     auto &lineFlags = item.lineModFlags;
0296 
0297     switch (item.type) {
0298     case UndoItem::editInsertText:
0299     case UndoItem::editRemoveText:
0300     case UndoItem::editRemoveLine:
0301         if (!wasBitSet) {
0302             lineFlags.setFlag(UndoItem::UndoLine1Modified, false);
0303             lineFlags.setFlag(UndoItem::UndoLine1Saved, true);
0304         }
0305         break;
0306     case UndoItem::editWrapLine:
0307         if (lineFlags.testFlag(UndoItem::UndoLine1Modified) && !wasBitSet) {
0308             lineFlags.setFlag(UndoItem::UndoLine1Modified, false);
0309             lineFlags.setFlag(UndoItem::UndoLine1Saved, true);
0310         }
0311         break;
0312     case UndoItem::editUnWrapLine:
0313         if (line + 1 >= lines.size()) {
0314             lines.resize(line + 2);
0315         }
0316         if (lineFlags.testFlag(UndoItem::UndoLine1Modified) && !wasBitSet) {
0317             lineFlags.setFlag(UndoItem::UndoLine1Modified, false);
0318             lineFlags.setFlag(UndoItem::UndoLine1Saved, true);
0319         }
0320 
0321         if (lineFlags.testFlag(UndoItem::UndoLine2Modified) && !lines.testBit(line + 1)) {
0322             lines.setBit(line + 1);
0323 
0324             lineFlags.setFlag(UndoItem::UndoLine2Modified, false);
0325             lineFlags.setFlag(UndoItem::UndoLine2Saved, true);
0326         }
0327         break;
0328     case UndoItem::editInsertLine:
0329     case UndoItem::editMarkLineAutoWrapped:
0330     case UndoItem::editInvalid:
0331         break;
0332     }
0333 }
0334 
0335 void KateUndoGroup::markUndoAsSaved(QBitArray &lines)
0336 {
0337     for (auto rit = m_items.rbegin(); rit != m_items.rend(); ++rit) {
0338         updateUndoSavedOnDiskFlag(*rit, lines);
0339     }
0340 }
0341 
0342 static void updateRedoSavedOnDiskFlag(UndoItem &item, QBitArray &lines)
0343 {
0344     const int line = item.line;
0345     if (line >= lines.size()) {
0346         lines.resize(line + 1);
0347     }
0348 
0349     const bool wasBitSet = lines.testBit(line);
0350     if (!wasBitSet) {
0351         lines.setBit(line);
0352     }
0353     auto &lineFlags = item.lineModFlags;
0354 
0355     switch (item.type) {
0356     case UndoItem::editInsertText:
0357     case UndoItem::editRemoveText:
0358     case UndoItem::editInsertLine:
0359         lineFlags.setFlag(UndoItem::RedoLine1Modified, false);
0360         lineFlags.setFlag(UndoItem::RedoLine1Saved, true);
0361         break;
0362     case UndoItem::editUnWrapLine:
0363         if (lineFlags.testFlag(UndoItem::RedoLine1Modified) && !wasBitSet) {
0364             lineFlags.setFlag(UndoItem::RedoLine1Modified, false);
0365             lineFlags.setFlag(UndoItem::RedoLine1Saved, true);
0366         }
0367         break;
0368     case UndoItem::editWrapLine:
0369         if (line + 1 >= lines.size()) {
0370             lines.resize(line + 2);
0371         }
0372 
0373         if (lineFlags.testFlag(UndoItem::RedoLine1Modified) && !wasBitSet) {
0374             lineFlags.setFlag(UndoItem::RedoLine1Modified, false);
0375             lineFlags.setFlag(UndoItem::RedoLine1Saved, true);
0376         }
0377 
0378         if (lineFlags.testFlag(UndoItem::RedoLine2Modified) && !lines.testBit(line + 1)) {
0379             lineFlags.setFlag(UndoItem::RedoLine2Modified, false);
0380             lineFlags.setFlag(UndoItem::RedoLine2Saved, true);
0381         }
0382         break;
0383     case UndoItem::editRemoveLine:
0384     case UndoItem::editMarkLineAutoWrapped:
0385     case UndoItem::editInvalid:
0386         break;
0387     }
0388 }
0389 
0390 void KateUndoGroup::markRedoAsSaved(QBitArray &lines)
0391 {
0392     for (auto rit = m_items.rbegin(); rit != m_items.rend(); ++rit) {
0393         updateRedoSavedOnDiskFlag(*rit, lines);
0394     }
0395 }
0396 
0397 UndoItem::UndoType KateUndoGroup::singleType() const
0398 {
0399     UndoItem::UndoType ret = UndoItem::editInvalid;
0400 
0401     for (const auto &item : m_items) {
0402         if (ret == UndoItem::editInvalid) {
0403             ret = item.type;
0404         } else if (ret != item.type) {
0405             return UndoItem::editInvalid;
0406         }
0407     }
0408 
0409     return ret;
0410 }
0411 
0412 bool KateUndoGroup::isOnlyType(UndoItem::UndoType type) const
0413 {
0414     if (type == UndoItem::editInvalid) {
0415         return false;
0416     }
0417     for (const auto &item : m_items) {
0418         if (item.type != type)
0419             return false;
0420     }
0421     return true;
0422 }