Skip to content

Commit

Permalink
fix(Studio): Remaining issues with patch-merging
Browse files Browse the repository at this point in the history
  • Loading branch information
psyGamer committed Oct 8, 2024
1 parent 3f1cb2c commit 2e49d47
Showing 1 changed file with 44 additions and 28 deletions.
72 changes: 44 additions & 28 deletions Studio/CelesteStudio/Editing/Document.cs
Original file line number Diff line number Diff line change
Expand Up @@ -430,6 +430,8 @@ public QueuedUpdate(Document document, bool raiseEvents) {
document.undoStack.PrepareState(document);
}

/// Pushes the modification onto the undo-stack and raises events if enabled
/// Automatically called with the using-syntax
public void Dispose() {
Document.updateStack.Pop();

Expand Down Expand Up @@ -471,32 +473,38 @@ public void Dispose() {

/// Represents a small modification of the document with insertions and deletions
public readonly struct Patch(Document document, QueuedUpdate? update = null) : IDisposable {
// Insertions are at the lines where the new text will be
// Deletions are at the lines where the line currently is
public readonly Dictionary<int, string> Insertions = [], Deletions = [];
/// Insertions at the lines where the new text will be (after deletions are applied!)
public readonly Dictionary<int, string> Insertions = [];
/// Deletions at the lines where the line currently is (before insertions are applied!)
public readonly Dictionary<int, string> Deletions = [];

private readonly QueuedUpdate update = update ?? document.updateStack.Peek();

/// Inserts the line at the specified row
public void Insert(int row, string line) {
Insertions[row] = line;
}
/// Inserts the lines, starting at the specified row
public void InsertRange(int row, IEnumerable<string> lines) {
foreach (string line in lines) {
Insertions[row++] = line;
}
}

/// Removes the specified row
public void Delete(int row) {
if (!Insertions.Remove(row)) {
Deletions.Add(row, document.CurrentLines[row]);
}
}
/// Removes an inclusive range from minRow..maxRow
public void DeleteRange(int minRow, int maxRow) {
for (int row = minRow; row <= maxRow; row++) {
Delete(row);
}
}

/// Replaces a single line
public void Modify(int row, string line) {
if (!Insertions.Remove(row)) {
Deletions.Add(row, document.CurrentLines[row]);
Expand All @@ -515,6 +523,8 @@ public void CleanupNoOps() {
}
}

/// Applies the patch to the document and adds it to the update
/// Automatically called with the using-syntax
public void Dispose() {
CleanupNoOps();
if (Insertions.Count == 0 && Deletions.Count == 0) {
Expand All @@ -525,6 +535,7 @@ public void Dispose() {
update.Patches.Add(this);
}

/// Creates a deep copy
public Patch Copy() {
var patch = new Patch(document, update);
foreach ((int row, string line) in Insertions) {
Expand All @@ -535,6 +546,7 @@ public Patch Copy() {
}
return patch;
}
/// Creates a deep copy with insertions / deletions swapped
public Patch CopySwapped() {
var patch = new Patch(document, update);
foreach ((int row, string line) in Insertions) {
Expand All @@ -546,47 +558,51 @@ public Patch CopySwapped() {
return patch;
}

/// Merges the patches A and B, where A is based on the initial state and B is based on after A is applied
public static Patch Merge(Patch a, Patch b) {
// Cancel out deletions / insertions
foreach (var row in a.Insertions.Keys.Intersect(b.Deletions.Keys)) {
a.Insertions.Remove(row);
b.Deletions.Remove(row);
}

List<int> keysToShift = [];
List<int> rowsToShift = [];

// Shift down deletions in B
foreach ((int row, _) in a.Deletions.OrderBy(entry => entry.Key).Reverse()) {
keysToShift.AddRange(b.Deletions.Keys.Where(key => key >= row));
}
foreach (int key in keysToShift) {
string value = b.Deletions[key];
b.Deletions[key + 1] = value;
b.Deletions.Remove(key);
foreach ((int aRow, _) in a.Deletions.OrderBy(entry => entry.Key)) {
rowsToShift.Clear();
rowsToShift.AddRange(b.Deletions.Keys.Where(bRow => bRow >= aRow));

foreach (int row in rowsToShift.OrderBy(row => row)) {
string value = b.Deletions[row];
b.Deletions[row + 1] = value;
b.Deletions.Remove(row);
}
}
keysToShift.Clear();

// Shift up deletions in B
foreach ((int row, _) in a.Insertions.OrderBy(entry => entry.Key)) {
keysToShift.AddRange(b.Deletions.Keys.Where(key => key > row));
}
foreach (int key in keysToShift) {
string value = a.Insertions[key];
a.Insertions[key - 1] = value;
a.Insertions.Remove(key);
foreach ((int aRow, _) in a.Insertions.OrderBy(entry => entry.Key).Reverse()) {
rowsToShift.Clear();
rowsToShift.AddRange(b.Deletions.Keys.Where(bRow => bRow > aRow));

foreach (int row in rowsToShift.OrderBy(row => row).Reverse()) {
string value = b.Deletions[row];
b.Deletions[row - 1] = value;
b.Deletions.Remove(row);
}
}
keysToShift.Clear();

// Shift down insertions in A
foreach ((int row, _) in b.Insertions.OrderBy(entry => entry.Key).Reverse()) {
keysToShift.AddRange(a.Insertions.Keys.Where(key => key >= row));
}
foreach (int key in keysToShift) {
string value = a.Insertions[key];
a.Insertions[key + 1] = value;
a.Insertions.Remove(key);
foreach ((int bRow, _) in b.Insertions.OrderBy(entry => entry.Key)) {
rowsToShift.Clear();
rowsToShift.AddRange(a.Insertions.Keys.Where(aRow => aRow >= bRow));

foreach (int row in rowsToShift.OrderBy(row => row)) {
string value = b.Deletions[row];
b.Deletions[row + 1] = value;
b.Deletions.Remove(row);
}
}
keysToShift.Clear();

// Merge
foreach ((int row, string line) in b.Insertions) {
Expand Down

0 comments on commit 2e49d47

Please sign in to comment.