aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/ChangeHistory.cxx
diff options
context:
space:
mode:
authorNeil <nyamatongwe@gmail.com>2022-07-31 15:51:53 +1000
committerNeil <nyamatongwe@gmail.com>2022-07-31 15:51:53 +1000
commit926cb6f7d228b347db16a45e1f2632da475da1f0 (patch)
tree6263662cb8c023502d61b61eed34baa93ed35843 /src/ChangeHistory.cxx
parent535e20189d5a2dd9b43a6ea0a74749a50678d631 (diff)
downloadscintilla-mirror-926cb6f7d228b347db16a45e1f2632da475da1f0.tar.gz
Added change history which can display document changes (modified, saved, ...)
in the margin or in the text.
Diffstat (limited to 'src/ChangeHistory.cxx')
-rw-r--r--src/ChangeHistory.cxx422
1 files changed, 422 insertions, 0 deletions
diff --git a/src/ChangeHistory.cxx b/src/ChangeHistory.cxx
new file mode 100644
index 000000000..4c771a93c
--- /dev/null
+++ b/src/ChangeHistory.cxx
@@ -0,0 +1,422 @@
+// Scintilla source code edit control
+/** @file ChangeHistory.cxx
+ ** Manages a history of changes in a document.
+ **/
+// Copyright 2022 by Neil Hodgson <neilh@scintilla.org>
+// The License.txt file describes the conditions under which this software may be distributed.
+
+#include <cstddef>
+#include <cstdlib>
+#include <cassert>
+
+#include <stdexcept>
+#include <vector>
+#include <set>
+#include <algorithm>
+#include <memory>
+
+#include "ScintillaTypes.h"
+
+#include "Debugging.h"
+
+#include "Position.h"
+#include "SplitVector.h"
+#include "Partitioning.h"
+#include "RunStyles.h"
+#include "SparseVector.h"
+#include "ChangeHistory.h"
+
+namespace Scintilla::Internal {
+
+void ChangeStack::Clear() noexcept {
+ steps.clear();
+ insertions.clear();
+}
+
+void ChangeStack::AddStep() {
+ steps.push_back(0);
+}
+
+void ChangeStack::PushDeletion(Sci::Position positionDeletion, int edition) {
+ steps.back()++;
+ insertions.push_back({ positionDeletion, 0, edition, InsertionSpan::Direction::deletion });
+}
+
+void ChangeStack::PushInsertion(Sci::Position positionInsertion, Sci::Position length, int edition) {
+ steps.back()++;
+ insertions.push_back({ positionInsertion, length, edition, InsertionSpan::Direction::insertion });
+};
+
+size_t ChangeStack::PopStep() noexcept {
+ const size_t spans = steps.back();
+ steps.pop_back();
+ return spans;
+}
+
+InsertionSpan ChangeStack::PopSpan() noexcept {
+ const InsertionSpan span = insertions.back();
+ insertions.pop_back();
+ return span;
+}
+
+void ChangeStack::SetSavePoint() noexcept {
+ // Switch changeUnsaved to changeSaved
+ for (InsertionSpan &x : insertions) {
+ if (x.edition == changeModified) {
+ x.edition = changeSaved;
+ }
+ }
+}
+
+void ChangeLog::Clear(Sci::Position length) {
+ changeStack.Clear();
+ insertEdition.DeleteAll();
+ deleteEdition.DeleteAll();
+ InsertSpace(0, length);
+}
+
+void ChangeLog::InsertSpace(Sci::Position position, Sci::Position insertLength) {
+ assert(insertEdition.Length() == deleteEdition.Length());
+ insertEdition.InsertSpace(position, insertLength);
+ deleteEdition.InsertSpace(position, insertLength);
+}
+
+void ChangeLog::DeleteRange(Sci::Position position, Sci::Position deleteLength) {
+ insertEdition.DeleteRange(position, deleteLength);
+ const EditionSetOwned &editions = deleteEdition.ValueAt(position);
+ if (editions) {
+ const EditionSet savedEditions = *editions;
+ deleteEdition.DeleteRange(position, deleteLength);
+ EditionSetOwned reset = std::make_unique<EditionSet>(savedEditions);
+ deleteEdition.SetValueAt(position, std::move(reset));
+ } else {
+ deleteEdition.DeleteRange(position, deleteLength);
+ }
+ assert(insertEdition.Length() == deleteEdition.Length());
+}
+
+void ChangeLog::Insert(Sci::Position start, Sci::Position length, int edition) {
+ insertEdition.FillRange(start, edition, length);
+}
+
+void ChangeLog::CollapseRange(Sci::Position position, Sci::Position deleteLength) {
+ const Sci::Position positionMax = position + deleteLength;
+ Sci::Position positionDeletion = position + 1;
+ while (positionDeletion <= positionMax) {
+ const EditionSetOwned &editions = deleteEdition.ValueAt(positionDeletion);
+ if (editions) {
+ for (const int ed : *editions) {
+ PushDeletionAt(position, ed);
+ }
+ EditionSetOwned empty;
+ deleteEdition.SetValueAt(positionDeletion, std::move(empty));
+ }
+ positionDeletion = deleteEdition.PositionNext(positionDeletion);
+ }
+}
+
+void ChangeLog::PushDeletionAt(Sci::Position position, int edition) {
+ if (!deleteEdition.ValueAt(position)) {
+ deleteEdition.SetValueAt(position, std::make_unique<EditionSet>());
+ }
+ deleteEdition.ValueAt(position)->push_back(edition);
+}
+
+void ChangeLog::InsertFrontDeletionAt(Sci::Position position, int edition) {
+ if (!deleteEdition.ValueAt(position)) {
+ deleteEdition.SetValueAt(position, std::make_unique<EditionSet>());
+ }
+ const EditionSetOwned &editions = deleteEdition.ValueAt(position);
+ editions->insert(editions->begin(), edition);
+}
+
+void ChangeLog::SaveRange(Sci::Position position, Sci::Position length) {
+ // Save insertEdition range into undo stack
+ changeStack.AddStep();
+ Sci::Position positionInsertion = position;
+ const ptrdiff_t editionStart = insertEdition.ValueAt(positionInsertion);
+ if (editionStart == 0) {
+ positionInsertion = insertEdition.EndRun(positionInsertion);
+ }
+ const Sci::Position positionMax = position + length;
+ while (positionInsertion < positionMax) {
+ const Sci::Position positionEndInsertion = insertEdition.EndRun(positionInsertion);
+ changeStack.PushInsertion(positionInsertion, std::min(positionEndInsertion, positionMax) - positionInsertion,
+ insertEdition.ValueAt(positionInsertion));
+ positionInsertion = insertEdition.EndRun(positionEndInsertion);
+ }
+ Sci::Position positionDeletion = position + 1;
+ while (positionDeletion <= positionMax) {
+ const EditionSetOwned &editions = deleteEdition.ValueAt(positionDeletion);
+ if (editions) {
+ for (const int ed : *editions) {
+ changeStack.PushDeletion(positionDeletion, ed);
+ }
+ }
+ positionDeletion = deleteEdition.PositionNext(positionDeletion);
+ }
+}
+
+void ChangeLog::PopDeletion(Sci::Position position, Sci::Position deleteLength) {
+ // Just performed InsertSpace(position, deleteLength) so *this* element in
+ // deleteEdition moved forward by deleteLength
+ EditionSetOwned eso = deleteEdition.Extract(position + deleteLength);
+ deleteEdition.SetValueAt(position, std::move(eso));
+ const EditionSetOwned &editions = deleteEdition.ValueAt(position);
+ assert(editions);
+ editions->pop_back();
+ const size_t inserts = changeStack.PopStep();
+ for (size_t i = 0; i < inserts; i++) {
+ const InsertionSpan span = changeStack.PopSpan();
+ if (span.direction == InsertionSpan::Direction::insertion) {
+ insertEdition.FillRange(span.start, span.edition, span.length);
+ } else {
+ assert(editions);
+ assert(editions->back() == span.edition);
+ editions->pop_back();
+ InsertFrontDeletionAt(span.start, span.edition);
+ }
+ }
+
+ if (editions->empty()) {
+ deleteEdition.SetValueAt(position, EditionSetOwned{});
+ }
+}
+
+void ChangeLog::SaveHistoryForDelete(Sci::Position position, Sci::Position deleteLength) {
+ assert(position >= 0);
+ assert(deleteLength >= 0);
+ assert(position + deleteLength <= Length());
+ SaveRange(position, deleteLength);
+ CollapseRange(position, deleteLength);
+}
+
+void ChangeLog::DeleteRangeSavingHistory(Sci::Position position, Sci::Position deleteLength) {
+ SaveHistoryForDelete(position, deleteLength);
+ DeleteRange(position, deleteLength);
+}
+
+void ChangeLog::SetSavePoint() {
+ // Switch changeUnsaved to changeSaved
+ changeStack.SetSavePoint();
+
+ const Sci::Position length = insertEdition.Length();
+
+ for (Sci::Position startRun = 0; startRun < length;) {
+ const Sci::Position endRun = insertEdition.EndRun(startRun);
+ if (insertEdition.ValueAt(startRun) == changeModified) {
+ insertEdition.FillRange(startRun, changeSaved, endRun - startRun);
+ }
+ startRun = endRun;
+ }
+
+ for (Sci::Position positionDeletion = 0; positionDeletion <= length;) {
+ const EditionSetOwned &editions = deleteEdition.ValueAt(positionDeletion);
+ if (editions) {
+ for (int &ed : *editions) {
+ if (ed == changeModified) {
+ ed = changeSaved;
+ }
+ }
+ }
+ positionDeletion = deleteEdition.PositionNext(positionDeletion);
+ }
+}
+
+Sci::Position ChangeLog::Length() const noexcept {
+ return insertEdition.Length();
+}
+
+size_t ChangeLog::DeletionCount(Sci::Position start, Sci::Position length) const noexcept {
+ const Sci::Position end = start + length;
+ size_t count = 0;
+ while (start <= end) {
+ const EditionSetOwned &editions = deleteEdition.ValueAt(start);
+ if (editions) {
+ count += editions->size();
+ }
+ start = deleteEdition.PositionNext(start);
+ }
+ return count;
+}
+
+void ChangeLog::Check() const noexcept {
+ assert(insertEdition.Length() == deleteEdition.Length());
+}
+
+ChangeHistory::ChangeHistory(Sci::Position length) {
+ changeLog.Clear(length);
+}
+
+void ChangeHistory::Insert(Sci::Position position, Sci::Position insertLength, bool collectingUndo, bool beforeSave) {
+ Check();
+ changeLog.InsertSpace(position, insertLength);
+ const int edition = collectingUndo ? (beforeSave ? changeSaved : changeModified) :
+ changeOriginal;
+ changeLog.Insert(position, insertLength, edition);
+ if (changeLogReversions) {
+ changeLogReversions->InsertSpace(position, insertLength);
+ if (beforeSave) {
+ changeLogReversions->PopDeletion(position, insertLength);
+ }
+ }
+ Check();
+}
+
+void ChangeHistory::DeleteRange(Sci::Position position, Sci::Position deleteLength, bool reverting) {
+ Check();
+ assert(DeletionCount(position, deleteLength-1) == 0);
+ changeLog.DeleteRange(position, deleteLength);
+ if (changeLogReversions) {
+ changeLogReversions->DeleteRangeSavingHistory(position, deleteLength);
+ if (reverting) {
+ changeLogReversions->PushDeletionAt(position, 1);
+ }
+ }
+ Check();
+}
+
+void ChangeHistory::DeleteRangeSavingHistory(Sci::Position position, Sci::Position deleteLength, bool beforeSave, bool isDetached) {
+ changeLog.DeleteRangeSavingHistory(position, deleteLength);
+ changeLog.PushDeletionAt(position, beforeSave ? changeSaved : changeModified);
+ if (changeLogReversions) {
+ if (isDetached) {
+ changeLogReversions->SaveHistoryForDelete(position, deleteLength);
+ }
+ changeLogReversions->DeleteRange(position, deleteLength);
+ }
+ Check();
+}
+
+void ChangeHistory::StartReversion() {
+ if (!changeLogReversions) {
+ changeLogReversions = std::make_unique<ChangeLog>();
+ changeLogReversions->Clear(changeLog.Length());
+ }
+ Check();
+}
+
+void ChangeHistory::EndReversion() noexcept {
+ changeLogReversions.reset();
+ Check();
+}
+
+void ChangeHistory::SetSavePoint() {
+ changeLog.SetSavePoint();
+ EndReversion();
+}
+
+void ChangeHistory::UndoDeleteStep(Sci::Position position, Sci::Position deleteLength, bool isDetached) {
+ Check();
+ changeLog.InsertSpace(position, deleteLength);
+ changeLog.PopDeletion(position, deleteLength);
+ if (changeLogReversions) {
+ changeLogReversions->InsertSpace(position, deleteLength);
+ if (!isDetached) {
+ changeLogReversions->Insert(position, deleteLength, 1);
+ }
+ }
+ Check();
+}
+
+Sci::Position ChangeHistory::Length() const noexcept {
+ return changeLog.Length();
+}
+
+void ChangeHistory::SetEpoch(int epoch) noexcept {
+ historicEpoch = epoch;
+}
+
+void ChangeHistory::EditionCreateHistory(Sci::Position start, Sci::Position length) {
+ if (start <= changeLog.Length()) {
+ if (length) {
+ changeLog.insertEdition.FillRange(start, historicEpoch, length);
+ } else {
+ changeLog.PushDeletionAt(start, historicEpoch);
+ }
+ }
+}
+
+// Editions:
+// <0 History
+// 0 Original unchanged
+// 1 Reverted to origin
+// 2 Saved
+// 3 Unsaved
+// 4 Reverted to change
+int ChangeHistory::EditionAt(Sci::Position pos) const noexcept {
+ const int edition = changeLog.insertEdition.ValueAt(pos);
+ if (changeLogReversions) {
+ const int editionReversion = changeLogReversions->insertEdition.ValueAt(pos);
+ if (editionReversion) {
+ if (edition < 0)
+ return 1;
+ return edition ? 4 : 1;
+ }
+ }
+ return edition;
+}
+
+Sci::Position ChangeHistory::EditionEndRun(Sci::Position pos) const noexcept {
+ if (changeLogReversions) {
+ assert(changeLogReversions->Length() == changeLog.Length());
+ const Sci::Position nextReversion = changeLogReversions->insertEdition.EndRun(pos);
+ const Sci::Position next = changeLog.insertEdition.EndRun(pos);
+ return std::min(next, nextReversion);
+ }
+ return changeLog.insertEdition.EndRun(pos);
+}
+
+// Produce a 4-bit value from the deletions at a position
+unsigned int ChangeHistory::EditionDeletesAt(Sci::Position pos) const noexcept {
+ unsigned int editionSet = 0;
+ const EditionSetOwned &editionSetDeletions = changeLog.deleteEdition.ValueAt(pos);
+ if (editionSetDeletions) {
+ for (const unsigned int ed : *editionSetDeletions) {
+ editionSet = editionSet | (1u << (ed-1));
+ }
+ }
+ if (changeLogReversions) {
+ const EditionSetOwned &editionSetReversions = changeLogReversions->deleteEdition.ValueAt(pos);
+ if (editionSetReversions) {
+ // If there is no saved or modified -> revertedToOrigin
+ if (!(editionSet & (bitSaved | bitModified))) {
+ editionSet = editionSet | bitRevertedToOriginal;
+ } else {
+ editionSet = editionSet | bitRevertedToModified;
+ }
+ }
+ }
+ return editionSet;
+}
+
+Sci::Position ChangeHistory::EditionNextDelete(Sci::Position pos) const noexcept {
+ const Sci::Position next = changeLog.deleteEdition.PositionNext(pos);
+ if (changeLogReversions) {
+ const Sci::Position nextReversion = changeLogReversions->deleteEdition.PositionNext(pos);
+ return std::min(next, nextReversion);
+ }
+ return next;
+}
+
+size_t ChangeHistory::DeletionCount(Sci::Position start, Sci::Position length) const noexcept {
+ return changeLog.DeletionCount(start, length);
+}
+
+EditionSet ChangeHistory::DeletionsAt(Sci::Position pos) const {
+ const EditionSetOwned &editions = changeLog.deleteEdition.ValueAt(pos);
+ if (editions) {
+ return *editions;
+ }
+ return {};
+}
+
+void ChangeHistory::Check() noexcept {
+ changeLog.Check();
+ if (changeLogReversions) {
+ changeLogReversions->Check();
+ assert(changeLogReversions->Length() == changeLog.Length());
+ }
+}
+
+}