File indexing completed on 2024-07-21 03:42:56

0001 /*
0002     SPDX-FileCopyrightText: 2013 Christoph Cullmann <cullmann@kde.org>
0003 
0004     SPDX-License-Identifier: LGPL-2.0-or-later
0005 */
0006 
0007 #include "katetexthistory.h"
0008 #include "katetextbuffer.h"
0009 
0010 namespace Kate
0011 {
0012 TextHistory::TextHistory(TextBuffer &buffer)
0013     : m_buffer(buffer)
0014     , m_lastSavedRevision(-1)
0015     , m_firstHistoryEntryRevision(0)
0016 {
0017     // just call clear to init
0018     clear();
0019 }
0020 
0021 TextHistory::~TextHistory() = default;
0022 
0023 qint64 TextHistory::revision() const
0024 {
0025     // just output last revisions of buffer
0026     return m_buffer.revision();
0027 }
0028 
0029 void TextHistory::clear()
0030 {
0031     // reset last saved revision
0032     m_lastSavedRevision = -1;
0033 
0034     // remove all history entries and add no-change dummy for first revision
0035     m_historyEntries.clear();
0036     m_historyEntries.push_back(Entry());
0037 
0038     // first entry will again belong to first revision
0039     m_firstHistoryEntryRevision = 0;
0040 }
0041 
0042 void TextHistory::setLastSavedRevision()
0043 {
0044     // current revision was successful saved
0045     m_lastSavedRevision = revision();
0046 }
0047 
0048 void TextHistory::wrapLine(const KTextEditor::Cursor position)
0049 {
0050     // create and add new entry
0051     Entry entry;
0052     entry.type = Entry::WrapLine;
0053     entry.line = position.line();
0054     entry.column = position.column();
0055     addEntry(entry);
0056 }
0057 
0058 void TextHistory::unwrapLine(int line, int oldLineLength)
0059 {
0060     // create and add new entry
0061     Entry entry;
0062     entry.type = Entry::UnwrapLine;
0063     entry.line = line;
0064     entry.column = 0;
0065     entry.oldLineLength = oldLineLength;
0066     addEntry(entry);
0067 }
0068 
0069 void TextHistory::insertText(const KTextEditor::Cursor position, int length, int oldLineLength)
0070 {
0071     // create and add new entry
0072     Entry entry;
0073     entry.type = Entry::InsertText;
0074     entry.line = position.line();
0075     entry.column = position.column();
0076     entry.length = length;
0077     entry.oldLineLength = oldLineLength;
0078     addEntry(entry);
0079 }
0080 
0081 void TextHistory::removeText(KTextEditor::Range range, int oldLineLength)
0082 {
0083     // create and add new entry
0084     Entry entry;
0085     entry.type = Entry::RemoveText;
0086     entry.line = range.start().line();
0087     entry.column = range.start().column();
0088     entry.length = range.end().column() - range.start().column();
0089     entry.oldLineLength = oldLineLength;
0090     addEntry(entry);
0091 }
0092 
0093 void TextHistory::addEntry(const Entry &entry)
0094 {
0095     // history should never be empty
0096     Q_ASSERT(!m_historyEntries.empty());
0097 
0098     // simple efficient check: if we only have one entry, and the entry is not referenced
0099     // just replace it with the new one and adjust the revision
0100     if ((m_historyEntries.size() == 1) && !m_historyEntries.front().referenceCounter) {
0101         // remember new revision for first element, it is the revision we get after this change
0102         m_firstHistoryEntryRevision = revision() + 1;
0103 
0104         // remember edit
0105         m_historyEntries.front() = entry;
0106 
0107         // be done...
0108         return;
0109     }
0110 
0111     // ok, we have more than one entry or the entry is referenced, just add up new entries
0112     m_historyEntries.push_back(entry);
0113 }
0114 
0115 void TextHistory::lockRevision(qint64 revision)
0116 {
0117     // some invariants must hold
0118     Q_ASSERT(!m_historyEntries.empty());
0119     Q_ASSERT(revision >= m_firstHistoryEntryRevision);
0120     Q_ASSERT(revision < (m_firstHistoryEntryRevision + qint64(m_historyEntries.size())));
0121 
0122     // increment revision reference counter
0123     Entry &entry = m_historyEntries[revision - m_firstHistoryEntryRevision];
0124     ++entry.referenceCounter;
0125 }
0126 
0127 void TextHistory::unlockRevision(qint64 revision)
0128 {
0129     // some invariants must hold
0130     Q_ASSERT(!m_historyEntries.empty());
0131     Q_ASSERT(revision >= m_firstHistoryEntryRevision);
0132     Q_ASSERT(revision < (m_firstHistoryEntryRevision + qint64(m_historyEntries.size())));
0133 
0134     // decrement revision reference counter
0135     Entry &entry = m_historyEntries[revision - m_firstHistoryEntryRevision];
0136     Q_ASSERT(entry.referenceCounter);
0137     --entry.referenceCounter;
0138 
0139     // clean up no longer used revisions...
0140     if (!entry.referenceCounter) {
0141         // search for now unused stuff
0142         qint64 unreferencedEdits = 0;
0143         for (qint64 i = 0; i + 1 < qint64(m_historyEntries.size()); ++i) {
0144             if (m_historyEntries[i].referenceCounter) {
0145                 break;
0146             }
0147 
0148             // remember deleted count
0149             ++unreferencedEdits;
0150         }
0151 
0152         // remove unreferred from the list now
0153         if (unreferencedEdits > 0) {
0154             // remove stuff from history
0155             m_historyEntries.erase(m_historyEntries.begin(), m_historyEntries.begin() + unreferencedEdits);
0156 
0157             // patch first entry revision
0158             m_firstHistoryEntryRevision += unreferencedEdits;
0159         }
0160     }
0161 }
0162 
0163 void TextHistory::Entry::transformCursor(int &cursorLine, int &cursorColumn, bool moveOnInsert) const
0164 {
0165     // simple stuff, sort out generic things
0166 
0167     // no change, if this change is in line behind cursor
0168     if (line > cursorLine) {
0169         return;
0170     }
0171 
0172     // handle all history types
0173     switch (type) {
0174     // Wrap a line
0175     case WrapLine:
0176         // we wrap this line
0177         if (cursorLine == line) {
0178             // skip cursors with too small column
0179             if (cursorColumn <= column) {
0180                 if (cursorColumn < column || !moveOnInsert) {
0181                     return;
0182                 }
0183             }
0184 
0185             // adjust column
0186             cursorColumn = cursorColumn - column;
0187         }
0188 
0189         // always increment cursor line
0190         cursorLine += 1;
0191         return;
0192 
0193     // Unwrap a line
0194     case UnwrapLine:
0195         // we unwrap this line, adjust column
0196         if (cursorLine == line) {
0197             cursorColumn += oldLineLength;
0198         }
0199 
0200         // decrease cursor line
0201         cursorLine -= 1;
0202         return;
0203 
0204     // Insert text
0205     case InsertText:
0206         // only interesting, if same line
0207         if (cursorLine != line) {
0208             return;
0209         }
0210 
0211         // skip cursors with too small column
0212         if (cursorColumn <= column) {
0213             if (cursorColumn < column || !moveOnInsert) {
0214                 return;
0215             }
0216         }
0217 
0218         // patch column of cursor
0219         if (cursorColumn <= oldLineLength) {
0220             cursorColumn += length;
0221         }
0222 
0223         // special handling if cursor behind the real line, e.g. non-wrapping cursor in block selection mode
0224         else if (cursorColumn < oldLineLength + length) {
0225             cursorColumn = oldLineLength + length;
0226         }
0227 
0228         return;
0229 
0230     // Remove text
0231     case RemoveText:
0232         // only interesting, if same line
0233         if (cursorLine != line) {
0234             return;
0235         }
0236 
0237         // skip cursors with too small column
0238         if (cursorColumn <= column) {
0239             return;
0240         }
0241 
0242         // patch column of cursor
0243         if (cursorColumn <= column + length) {
0244             cursorColumn = column;
0245         } else {
0246             cursorColumn -= length;
0247         }
0248 
0249         return;
0250 
0251     // nothing
0252     default:
0253         return;
0254     }
0255 }
0256 
0257 void TextHistory::Entry::reverseTransformCursor(int &cursorLine, int &cursorColumn, bool moveOnInsert) const
0258 {
0259     // handle all history types
0260     switch (type) {
0261     // Wrap a line
0262     case WrapLine:
0263         // ignore this line
0264         if (cursorLine <= line) {
0265             return;
0266         }
0267 
0268         // next line is unwrapped
0269         if (cursorLine == line + 1) {
0270             // adjust column
0271             cursorColumn = cursorColumn + column;
0272         }
0273 
0274         // always decrement cursor line
0275         cursorLine -= 1;
0276         return;
0277 
0278     // Unwrap a line
0279     case UnwrapLine:
0280         // ignore lines before unwrapped one
0281         if (cursorLine < line - 1) {
0282             return;
0283         }
0284 
0285         // we unwrap this line, try to adjust cursor column if needed
0286         if (cursorLine == line - 1) {
0287             // skip cursors with to small columns
0288             if (cursorColumn <= oldLineLength) {
0289                 if (cursorColumn < oldLineLength || !moveOnInsert) {
0290                     return;
0291                 }
0292             }
0293 
0294             cursorColumn -= oldLineLength;
0295         }
0296 
0297         // increase cursor line
0298         cursorLine += 1;
0299         return;
0300 
0301     // Insert text
0302     case InsertText:
0303         // only interesting, if same line
0304         if (cursorLine != line) {
0305             return;
0306         }
0307 
0308         // skip cursors with too small column
0309         if (cursorColumn <= column) {
0310             return;
0311         }
0312 
0313         // patch column of cursor
0314         if (cursorColumn - length < column) {
0315             cursorColumn = column;
0316         } else {
0317             cursorColumn -= length;
0318         }
0319 
0320         return;
0321 
0322     // Remove text
0323     case RemoveText:
0324         // only interesting, if same line
0325         if (cursorLine != line) {
0326             return;
0327         }
0328 
0329         // skip cursors with too small column
0330         if (cursorColumn <= column) {
0331             if (cursorColumn < column || !moveOnInsert) {
0332                 return;
0333             }
0334         }
0335 
0336         // patch column of cursor
0337         if (cursorColumn <= oldLineLength) {
0338             cursorColumn += length;
0339         }
0340 
0341         // special handling if cursor behind the real line, e.g. non-wrapping cursor in block selection mode
0342         else if (cursorColumn < oldLineLength + length) {
0343             cursorColumn = oldLineLength + length;
0344         }
0345         return;
0346 
0347     // nothing
0348     default:
0349         return;
0350     }
0351 }
0352 
0353 void TextHistory::transformCursor(int &line, int &column, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision)
0354 {
0355     // -1 special meaning for from/toRevision
0356     if (fromRevision == -1) {
0357         fromRevision = revision();
0358     }
0359 
0360     if (toRevision == -1) {
0361         toRevision = revision();
0362     }
0363 
0364     // shortcut, same revision
0365     if (fromRevision == toRevision) {
0366         return;
0367     }
0368 
0369     // some invariants must hold
0370     Q_ASSERT(!m_historyEntries.empty());
0371     Q_ASSERT(fromRevision != toRevision);
0372     Q_ASSERT(fromRevision >= m_firstHistoryEntryRevision);
0373     Q_ASSERT(fromRevision < (m_firstHistoryEntryRevision + qint64(m_historyEntries.size())));
0374     Q_ASSERT(toRevision >= m_firstHistoryEntryRevision);
0375     Q_ASSERT(toRevision < (m_firstHistoryEntryRevision + qint64(m_historyEntries.size())));
0376 
0377     // transform cursor
0378     bool moveOnInsert = insertBehavior == KTextEditor::MovingCursor::MoveOnInsert;
0379 
0380     // forward or reverse transform?
0381     if (toRevision > fromRevision) {
0382         for (int rev = fromRevision - m_firstHistoryEntryRevision + 1; rev <= (toRevision - m_firstHistoryEntryRevision); ++rev) {
0383             const Entry &entry = m_historyEntries.at(rev);
0384             entry.transformCursor(line, column, moveOnInsert);
0385         }
0386     } else {
0387         for (int rev = fromRevision - m_firstHistoryEntryRevision; rev >= (toRevision - m_firstHistoryEntryRevision + 1); --rev) {
0388             const Entry &entry = m_historyEntries.at(rev);
0389             entry.reverseTransformCursor(line, column, moveOnInsert);
0390         }
0391     }
0392 }
0393 
0394 void TextHistory::transformRange(KTextEditor::Range &range,
0395                                  KTextEditor::MovingRange::InsertBehaviors insertBehaviors,
0396                                  KTextEditor::MovingRange::EmptyBehavior emptyBehavior,
0397                                  qint64 fromRevision,
0398                                  qint64 toRevision)
0399 {
0400     // invalidate on empty?
0401     bool invalidateIfEmpty = emptyBehavior == KTextEditor::MovingRange::InvalidateIfEmpty;
0402     if (invalidateIfEmpty && range.end() <= range.start()) {
0403         range = KTextEditor::Range::invalid();
0404         return;
0405     }
0406 
0407     // -1 special meaning for from/toRevision
0408     if (fromRevision == -1) {
0409         fromRevision = revision();
0410     }
0411 
0412     if (toRevision == -1) {
0413         toRevision = revision();
0414     }
0415 
0416     // shortcut, same revision
0417     if (fromRevision == toRevision) {
0418         return;
0419     }
0420 
0421     // some invariants must hold
0422     Q_ASSERT(!m_historyEntries.empty());
0423     Q_ASSERT(fromRevision != toRevision);
0424     Q_ASSERT(fromRevision >= m_firstHistoryEntryRevision);
0425     Q_ASSERT(fromRevision < (m_firstHistoryEntryRevision + qint64(m_historyEntries.size())));
0426     Q_ASSERT(toRevision >= m_firstHistoryEntryRevision);
0427     Q_ASSERT(toRevision < (m_firstHistoryEntryRevision + qint64(m_historyEntries.size())));
0428 
0429     // transform cursors
0430 
0431     // first: copy cursors, without range association
0432     int startLine = range.start().line();
0433     int startColumn = range.start().column();
0434     int endLine = range.end().line();
0435     int endColumn = range.end().column();
0436 
0437     bool moveOnInsertStart = !(insertBehaviors & KTextEditor::MovingRange::ExpandLeft);
0438     bool moveOnInsertEnd = (insertBehaviors & KTextEditor::MovingRange::ExpandRight);
0439 
0440     // forward or reverse transform?
0441     if (toRevision > fromRevision) {
0442         for (int rev = fromRevision - m_firstHistoryEntryRevision + 1; rev <= (toRevision - m_firstHistoryEntryRevision); ++rev) {
0443             const Entry &entry = m_historyEntries.at(rev);
0444 
0445             entry.transformCursor(startLine, startColumn, moveOnInsertStart);
0446 
0447             entry.transformCursor(endLine, endColumn, moveOnInsertEnd);
0448 
0449             // got empty?
0450             if (endLine < startLine || (endLine == startLine && endColumn <= startColumn)) {
0451                 if (invalidateIfEmpty) {
0452                     range = KTextEditor::Range::invalid();
0453                     return;
0454                 } else {
0455                     // else normalize them
0456                     endLine = startLine;
0457                     endColumn = startColumn;
0458                 }
0459             }
0460         }
0461     } else {
0462         for (int rev = fromRevision - m_firstHistoryEntryRevision; rev >= (toRevision - m_firstHistoryEntryRevision + 1); --rev) {
0463             const Entry &entry = m_historyEntries.at(rev);
0464 
0465             entry.reverseTransformCursor(startLine, startColumn, moveOnInsertStart);
0466 
0467             entry.reverseTransformCursor(endLine, endColumn, moveOnInsertEnd);
0468 
0469             // got empty?
0470             if (endLine < startLine || (endLine == startLine && endColumn <= startColumn)) {
0471                 if (invalidateIfEmpty) {
0472                     range = KTextEditor::Range::invalid();
0473                     return;
0474                 } else {
0475                     // else normalize them
0476                     endLine = startLine;
0477                     endColumn = startColumn;
0478                 }
0479             }
0480         }
0481     }
0482 
0483     // now, copy cursors back
0484     range.setRange(KTextEditor::Cursor(startLine, startColumn), KTextEditor::Cursor(endLine, endColumn));
0485 }
0486 
0487 }