kde-workspace/kate/part/buffer/katetexthistory.cpp
Ivailo Monev f68295ea28 generic: move sub-projects from kde-baseapps [ci reset]
Signed-off-by: Ivailo Monev <xakepa10@gmail.com>
2022-05-14 21:56:54 +03:00

587 lines
15 KiB
C++

/* This file is part of the Kate project.
*
* Copyright (C) 2010 Christoph Cullmann <cullmann@kde.org>
*
* This library is free software; you can redistribute it and/or
* modify it under the terms of the GNU Library General Public
* License as published by the Free Software Foundation; either
* version 2 of the License, or (at your option) any later version.
*
* This library is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
* Library General Public License for more details.
*
* You should have received a copy of the GNU Library General Public License
* along with this library; see the file COPYING.LIB. If not, write to
* the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
* Boston, MA 02110-1301, USA.
*/
#include "katetexthistory.h"
#include "katetextbuffer.h"
namespace Kate {
TextHistory::TextHistory (TextBuffer &buffer)
: m_buffer (buffer)
, m_lastSavedRevision (-1)
, m_firstHistoryEntryRevision (0)
{
// just call clear to init
clear ();
}
TextHistory::~TextHistory ()
{
}
qint64 TextHistory::revision () const
{
// just output last revisions of buffer
return m_buffer.revision ();
}
void TextHistory::clear ()
{
// reset last saved revision
m_lastSavedRevision = -1;
// remove all history entries and add no-change dummy for first revision
m_historyEntries.clear ();
m_historyEntries.push_back (Entry ());
// first entry will again belong to first revision
m_firstHistoryEntryRevision = 0;
}
void TextHistory::setLastSavedRevision ()
{
// current revision was successful saved
m_lastSavedRevision = revision ();
}
void TextHistory::wrapLine (const KTextEditor::Cursor &position)
{
// create and add new entry
Entry entry;
entry.type = Entry::WrapLine;
entry.line = position.line ();
entry.column = position.column ();
addEntry (entry);
}
void TextHistory::unwrapLine (int line, int oldLineLength)
{
// create and add new entry
Entry entry;
entry.type = Entry::UnwrapLine;
entry.line = line;
entry.column = 0;
entry.oldLineLength = oldLineLength;
addEntry (entry);
}
void TextHistory::insertText (const KTextEditor::Cursor &position, int length, int oldLineLength)
{
// create and add new entry
Entry entry;
entry.type = Entry::InsertText;
entry.line = position.line ();
entry.column = position.column ();
entry.length = length;
entry.oldLineLength = oldLineLength;
addEntry (entry);
}
void TextHistory::removeText (const KTextEditor::Range &range, int oldLineLength)
{
// create and add new entry
Entry entry;
entry.type = Entry::RemoveText;
entry.line = range.start().line ();
entry.column = range.start().column ();
entry.length = range.end().column() - range.start().column();
entry.oldLineLength = oldLineLength;
addEntry (entry);
}
void TextHistory::addEntry (const Entry &entry)
{
/**
* history should never be empty
*/
Q_ASSERT (!m_historyEntries.empty ());
/**
* simple efficient check: if we only have one entry, and the entry is not referenced
* just replace it with the new one and adjust the revision
*/
if ((m_historyEntries.size () == 1) && !m_historyEntries.first().referenceCounter) {
/**
* remember new revision for first element, it is the revision we get after this change
*/
m_firstHistoryEntryRevision = revision () + 1;
/**
* remember edit
*/
m_historyEntries.first() = entry;
/**
* be done...
*/
return;
}
/**
* ok, we have more than one entry or the entry is referenced, just add up new entries
*/
m_historyEntries.push_back (entry);
}
void TextHistory::lockRevision (qint64 revision)
{
/**
* some invariants must hold
*/
Q_ASSERT (!m_historyEntries.empty ());
Q_ASSERT (revision >= m_firstHistoryEntryRevision);
Q_ASSERT (revision < (m_firstHistoryEntryRevision + m_historyEntries.size()));
/**
* increment revision reference counter
*/
Entry &entry = m_historyEntries[revision - m_firstHistoryEntryRevision];
++entry.referenceCounter;
}
void TextHistory::unlockRevision (qint64 revision)
{
/**
* some invariants must hold
*/
Q_ASSERT (!m_historyEntries.empty ());
Q_ASSERT (revision >= m_firstHistoryEntryRevision);
Q_ASSERT (revision < (m_firstHistoryEntryRevision + m_historyEntries.size()));
/**
* decrement revision reference counter
*/
Entry &entry = m_historyEntries[revision - m_firstHistoryEntryRevision];
Q_ASSERT (entry.referenceCounter);
--entry.referenceCounter;
/**
* clean up no longer used revisions...
*/
if (!entry.referenceCounter) {
/**
* search for now unused stuff
*/
int unreferencedEdits = 0;
for (int i = 0; i + 1 < m_historyEntries.size(); ++i) {
if (m_historyEntries.at(i).referenceCounter)
break;
// remember deleted count
++unreferencedEdits;
}
/**
* remove unrefed from the list now
*/
if (unreferencedEdits > 0) {
// remove stuff from history
m_historyEntries.erase (m_historyEntries.begin(), m_historyEntries.begin() + unreferencedEdits);
// patch first entry revision
m_firstHistoryEntryRevision += unreferencedEdits;
}
}
}
void TextHistory::Entry::transformCursor (int &cursorLine, int &cursorColumn, bool moveOnInsert) const
{
/**
* simple stuff, sort out generic things
*/
/**
* no change, if this change is in line behind cursor
*/
if (line > cursorLine)
return;
/**
* handle all history types
*/
switch (type) {
/**
* Wrap a line
*/
case WrapLine:
/**
* we wrap this line
*/
if (cursorLine == line) {
/**
* skip cursors with too small column
*/
if (cursorColumn <= column) {
if (cursorColumn < column || !moveOnInsert)
return;
}
/**
* adjust column
*/
cursorColumn = cursorColumn - column;
}
/**
* always increment cursor line
*/
cursorLine += 1;
return;
/**
* Unwrap a line
*/
case UnwrapLine:
/**
* we unwrap this line, adjust column
*/
if (cursorLine == line)
cursorColumn += oldLineLength;
/**
* decrease cursor line
*/
cursorLine -= 1;
return;
/**
* Insert text
*/
case InsertText:
/**
* only interesting, if same line
*/
if (cursorLine != line)
return;
// skip cursors with too small column
if (cursorColumn <= column)
if (cursorColumn < column || !moveOnInsert)
return;
// patch column of cursor
if (cursorColumn <= oldLineLength)
cursorColumn += length;
// special handling if cursor behind the real line, e.g. non-wrapping cursor in block selection mode
else if (cursorColumn < oldLineLength + length)
cursorColumn = oldLineLength + length;
return;
/**
* Remove text
*/
case RemoveText:
/**
* only interesting, if same line
*/
if (cursorLine != line)
return;
// skip cursors with too small column
if (cursorColumn <= column)
return;
// patch column of cursor
if (cursorColumn <= column + length)
cursorColumn = column;
else
cursorColumn -= length;
return;
/**
* nothing
*/
default:
return;
}
}
void TextHistory::Entry::reverseTransformCursor (int &cursorLine, int &cursorColumn, bool moveOnInsert) const
{
/**
* handle all history types
*/
switch (type) {
/**
* Wrap a line
*/
case WrapLine:
/**
* ignore this line
*/
if (cursorLine <= line)
return;
/**
* next line is unwrapped
*/
if (cursorLine == line + 1) {
/**
* adjust column
*/
cursorColumn = cursorColumn + column;
}
/**
* always decrement cursor line
*/
cursorLine -= 1;
return;
/**
* Unwrap a line
*/
case UnwrapLine:
/**
* ignore lines before unwrapped one
*/
if (cursorLine < line - 1)
return;
/**
* we unwrap this line, try to adjust cursor column if needed
*/
if (cursorLine == line - 1) {
/**
* skip cursors with to small columns
*/
if (cursorColumn <= oldLineLength) {
if (cursorColumn < oldLineLength || !moveOnInsert)
return;
}
cursorColumn -= oldLineLength;
}
/**
* increase cursor line
*/
cursorLine += 1;
return;
/**
* Insert text
*/
case InsertText:
/**
* only interesting, if same line
*/
if (cursorLine != line)
return;
// skip cursors with too small column
if (cursorColumn <= column)
return;
// patch column of cursor
if (cursorColumn - length < column)
cursorColumn = column;
else
cursorColumn -= length;
return;
/**
* Remove text
*/
case RemoveText:
/**
* only interesting, if same line
*/
if (cursorLine != line)
return;
// skip cursors with too small column
if (cursorColumn <= column)
if (cursorColumn < column || !moveOnInsert)
return;
// patch column of cursor
if (cursorColumn <= oldLineLength)
cursorColumn += length;
// special handling if cursor behind the real line, e.g. non-wrapping cursor in block selection mode
else if (cursorColumn < oldLineLength + length)
cursorColumn = oldLineLength + length;
return;
/**
* nothing
*/
default:
return;
}
}
void TextHistory::transformCursor (int& line, int& column, KTextEditor::MovingCursor::InsertBehavior insertBehavior, qint64 fromRevision, qint64 toRevision)
{
/**
* -1 special meaning for from/toRevision
*/
if (fromRevision == -1)
fromRevision = revision ();
if (toRevision == -1)
toRevision = revision ();
/**
* shortcut, same revision
*/
if (fromRevision == toRevision)
return;
/**
* some invariants must hold
*/
Q_ASSERT (!m_historyEntries.empty ());
Q_ASSERT (fromRevision != toRevision);
Q_ASSERT (fromRevision >= m_firstHistoryEntryRevision);
Q_ASSERT (fromRevision < (m_firstHistoryEntryRevision + m_historyEntries.size()));
Q_ASSERT (toRevision >= m_firstHistoryEntryRevision);
Q_ASSERT (toRevision < (m_firstHistoryEntryRevision + m_historyEntries.size()));
/**
* transform cursor
*/
bool moveOnInsert = insertBehavior == KTextEditor::MovingCursor::MoveOnInsert;
/**
* forward or reverse transform?
*/
if (toRevision > fromRevision) {
for (int rev = fromRevision - m_firstHistoryEntryRevision + 1; rev <= (toRevision - m_firstHistoryEntryRevision); ++rev) {
const Entry &entry = m_historyEntries.at(rev);
entry.transformCursor (line, column, moveOnInsert);
}
} else {
for (int rev = fromRevision - m_firstHistoryEntryRevision; rev >= (toRevision - m_firstHistoryEntryRevision + 1); --rev) {
const Entry &entry = m_historyEntries.at(rev);
entry.reverseTransformCursor (line, column, moveOnInsert);
}
}
}
void TextHistory::transformRange (KTextEditor::Range &range, KTextEditor::MovingRange::InsertBehaviors insertBehaviors, KTextEditor::MovingRange::EmptyBehavior emptyBehavior, qint64 fromRevision, qint64 toRevision)
{
/**
* invalidate on empty?
*/
bool invalidateIfEmpty = emptyBehavior == KTextEditor::MovingRange::InvalidateIfEmpty;
if (invalidateIfEmpty && range.end() <= range.start()) {
range = KTextEditor::Range::invalid();
return;
}
/**
* -1 special meaning for from/toRevision
*/
if (fromRevision == -1)
fromRevision = revision ();
if (toRevision == -1)
toRevision = revision ();
/**
* shortcut, same revision
*/
if (fromRevision == toRevision)
return;
/**
* some invariants must hold
*/
Q_ASSERT (!m_historyEntries.empty ());
Q_ASSERT (fromRevision != toRevision);
Q_ASSERT (fromRevision >= m_firstHistoryEntryRevision);
Q_ASSERT (fromRevision < (m_firstHistoryEntryRevision + m_historyEntries.size()));
Q_ASSERT (toRevision >= m_firstHistoryEntryRevision);
Q_ASSERT (toRevision < (m_firstHistoryEntryRevision + m_historyEntries.size()));
/**
* transform cursors
*/
// first: copy cursors, without range association
int startLine = range.start().line(), startColumn = range.start().column(), endLine = range.end().line(), endColumn = range.end().column();
bool moveOnInsertStart = !(insertBehaviors & KTextEditor::MovingRange::ExpandLeft);
bool moveOnInsertEnd = (insertBehaviors & KTextEditor::MovingRange::ExpandRight);
/**
* forward or reverse transform?
*/
if (toRevision > fromRevision) {
for (int rev = fromRevision - m_firstHistoryEntryRevision + 1; rev <= (toRevision - m_firstHistoryEntryRevision); ++rev) {
const Entry &entry = m_historyEntries.at(rev);
entry.transformCursor (startLine, startColumn, moveOnInsertStart);
entry.transformCursor (endLine, endColumn, moveOnInsertEnd);
// got empty?
if(endLine < startLine || (endLine == startLine && endColumn <= startColumn))
{
if (invalidateIfEmpty) {
range = KTextEditor::Range::invalid();
return;
}
else{
// else normalize them
endLine = startLine;
endColumn = startColumn;
}
}
}
} else {
for (int rev = fromRevision - m_firstHistoryEntryRevision ; rev >= (toRevision - m_firstHistoryEntryRevision + 1); --rev) {
const Entry &entry = m_historyEntries.at(rev);
entry.reverseTransformCursor (startLine, startColumn, moveOnInsertStart);
entry.reverseTransformCursor (endLine, endColumn, moveOnInsertEnd);
// got empty?
if(endLine < startLine || (endLine == startLine && endColumn <= startColumn))
{
if (invalidateIfEmpty) {
range = KTextEditor::Range::invalid();
return;
}
else{
// else normalize them
endLine = startLine;
endColumn = startColumn;
}
}
}
}
// now, copy cursors back
range.start().setLine(startLine);
range.start().setColumn(startColumn);
range.end().setLine(endLine);
range.end().setColumn(endColumn);
}
}