File indexing completed on 2023-09-24 04:11:28
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 }