This commit is contained in:
2026-06-17 01:27:09 +12:00
parent a795a38cfe
commit 317c8c3ad2
2 changed files with 202 additions and 102 deletions

1
README.md Normal file
View File

@@ -0,0 +1 @@
piece table with both undo and redo features.

View File

@@ -38,7 +38,14 @@ const Action = struct {
old_length: usize, old_length: usize,
}; };
const ActionGroup = union(ActionType) { const ActionGroup = struct {
const PieceRedo = struct {
piece: Piece,
index: usize,
};
/// holds the min amount of info inorder to restore a change
undo_action: union(ActionType) {
/// this will only occur when the table was already empty so boof it /// this will only occur when the table was already empty so boof it
append: void, append: void,
@@ -51,6 +58,18 @@ const ActionGroup = union(ActionType) {
/// happens when a piece is deletes (length set to zero) /// happens when a piece is deletes (length set to zero)
/// appends all modifed pieces to be restored /// appends all modifed pieces to be restored
delete: std.ArrayList(Action), delete: std.ArrayList(Action),
},
/// just keeps the function args, best way to redo something is to redo it
redo_action: union(ActionType) {
append: Piece,
insert: PieceRedo,
insert_splits_piece: PieceRedo,
delete: struct {
from: usize,
to: usize,
},
},
}; };
/// abstracted so i can implement redo /// abstracted so i can implement redo
@@ -59,20 +78,36 @@ const ActionList = struct {
actions: std.ArrayList(ActionGroup) = .empty, actions: std.ArrayList(ActionGroup) = .empty,
/// offsets from the last index i down to zero
undo_offset: usize = 0,
pub const empty = ActionList{}; pub const empty = ActionList{};
pub fn append(self: *Self, alloc: std.mem.Allocator, action_group: ActionGroup) !void { pub fn append(self: *Self, alloc: std.mem.Allocator, action_group: ActionGroup) !void {
if (self.undo_offset != 0) {
try self.actions.resize(alloc, self.actions.items.len - 1 - self.undo_offset);
self.undo_offset = 0;
}
try self.actions.append(alloc, action_group); try self.actions.append(alloc, action_group);
} }
pub fn pop(self: *Self) ?ActionGroup { pub fn undo(self: *Self) ?ActionGroup {
return self.actions.pop(); if (self.undo_offset == self.actions.items.len) return null;
defer self.undo_offset += 1;
return self.actions.items[self.actions.items.len - 1 - self.undo_offset];
}
pub fn redo(self: *Self) ?ActionGroup {
if (self.undo_offset == 0) return null;
self.undo_offset -= 1;
return self.actions.items[self.actions.items.len - 1 - self.undo_offset];
} }
pub fn deinit(self: *Self, alloc: std.mem.Allocator) void { pub fn deinit(self: *Self, alloc: std.mem.Allocator) void {
for (self.actions.items) |*act| { for (self.actions.items) |*act| {
switch (act.*) { switch (act.undo_action) {
.delete => act.delete.deinit(alloc), .delete => act.undo_action.delete.deinit(alloc),
else => continue, else => continue,
} }
} }
@@ -103,6 +138,92 @@ const PieceTable = struct {
/// piece : the piece to add /// piece : the piece to add
/// index : the index in the text where the new piece will be added /// index : the index in the text where the new piece will be added
pub fn insert(self: *Self, alloc: Alloc, piece: Piece, index: usize) !void { pub fn insert(self: *Self, alloc: Alloc, piece: Piece, index: usize) !void {
return self.insertWActionDisable(alloc, piece, index, false);
}
/// will delete all from (index) to (index)
/// self : piece table that is being operated on
/// alloc : allocator used for adding the actions to the action list.
/// from : the index to be deleted from
/// to : the index the deletion will stop
pub fn delete(self: *Self, alloc: std.mem.Allocator, from: usize, to: usize) !void {
return self.deleteWActionDisable(alloc, from, to, false);
}
fn deleteWActionDisable(self: *Self, alloc: std.mem.Allocator, from: usize, to: usize, action_disabled: bool) !void {
var currnet_index: usize = 0;
var edited_piece_index: usize = 0;
var delete_actions: std.ArrayList(Action) = .empty;
// from set
for (self.pieces.items, 0..) |p, i| {
// find the index where the split occurs
if (currnet_index + p.length > from) {
// we gotta split the piece
var current_piece = &self.pieces.items[i];
if (!action_disabled) try delete_actions.append(
alloc,
Action{
.piece_table_index = i,
.old_start = current_piece.start,
.old_length = current_piece.length,
},
);
current_piece.length -= from - currnet_index;
edited_piece_index = i;
break;
}
if (currnet_index + p.length <= from) currnet_index += p.length;
}
// to set
for (self.pieces.items, edited_piece_index..) |p, i| {
if (currnet_index + p.length < to) {
var current_piece = &self.pieces.items[i];
if (!action_disabled) try delete_actions.append(
alloc,
Action{
.piece_table_index = i,
.old_start = current_piece.start,
.old_length = current_piece.length,
},
);
current_piece.length = 0;
currnet_index += p.length;
}
if (currnet_index + p.length >= to) {
var current_piece = &self.pieces.items[i];
if (!action_disabled) try delete_actions.append(
alloc,
Action{
.piece_table_index = i,
.old_start = current_piece.start,
.old_length = current_piece.length,
},
);
// shrink it
current_piece.start += to - currnet_index;
current_piece.length -= to - currnet_index;
break;
}
}
if (!action_disabled) try self.actions.append(alloc, ActionGroup{
.undo_action = .{ .delete = delete_actions },
.redo_action = .{ .delete = .{ .from = from, .to = to } },
});
}
fn insertWActionDisable(self: *Self, alloc: Alloc, piece: Piece, index: usize, action_disabled: bool) !void {
var currnet_start: usize = 0; var currnet_start: usize = 0;
for (self.pieces.items, 0..) |p, i| { for (self.pieces.items, 0..) |p, i| {
@@ -126,18 +247,30 @@ const PieceTable = struct {
}; };
try self.pieces.replaceRange(alloc, i, 1, &pieces); try self.pieces.replaceRange(alloc, i, 1, &pieces);
try self.actions.append(alloc, .{ if (!action_disabled) try self.actions.append(alloc, ActionGroup{
.undo_action = .{
.insert_splits_piece = Action{ .insert_splits_piece = Action{
.piece_table_index = i, .piece_table_index = i,
.old_length = p.length, .old_length = p.length,
.old_start = p.start, .old_start = p.start,
}, },
},
.redo_action = .{ .insert_splits_piece = .{
.piece = piece,
.index = index,
} },
}); });
return; return;
} }
if (currnet_start + p.length == index) { if (currnet_start + p.length == index) {
try self.pieces.insert(alloc, i + 1, piece); try self.pieces.insert(alloc, i + 1, piece);
try self.actions.append(alloc, .{ .insert = i + 1 }); if (!action_disabled) try self.actions.append(alloc, ActionGroup{
.undo_action = .{ .insert = i + 1 },
.redo_action = .{ .insert = .{
.piece = piece,
.index = index,
} },
});
return; return;
} // just append after } // just append after
if (currnet_start + p.length < index) currnet_start += p.length; if (currnet_start + p.length < index) currnet_start += p.length;
@@ -145,99 +278,37 @@ const PieceTable = struct {
// if there are no items this runs // if there are no items this runs
try self.pieces.append(alloc, piece); try self.pieces.append(alloc, piece);
try self.actions.append(alloc, .append); if (!action_disabled) try self.actions.append(alloc, ActionGroup{
.undo_action = .append,
.redo_action = .{ .append = piece },
});
} }
/// will delete all from (index) to (index) pub fn redo(self: *Self, alloc: std.mem.Allocator) !void {
/// self : piece table that is being operated on const action_group = self.actions.redo() orelse return;
/// alloc : allocator used for adding the actions to the action list.
/// from : the index to be deleted from
/// to : the index the deletion will stop
pub fn delete(self: *Self, alloc: std.mem.Allocator, from: usize, to: usize) !void {
var currnet_index: usize = 0;
var edited_piece_index: usize = 0;
var delete_actions: std.ArrayList(Action) = .empty;
// from set switch (action_group.redo_action) {
for (self.pieces.items, 0..) |p, i| { .append => |piece| {
// find the index where the split occurs try self.insertWActionDisable(alloc, piece, 0, true);
if (currnet_index + p.length > from) {
// we gotta split the piece
var current_piece = &self.pieces.items[i];
try delete_actions.append(
alloc,
Action{
.piece_table_index = i,
.old_start = current_piece.start,
.old_length = current_piece.length,
}, },
); .insert, .insert_splits_piece => |action| {
try self.insertWActionDisable(alloc, action.piece, action.index, true);
current_piece.length -= from - currnet_index;
edited_piece_index = i;
break;
}
if (currnet_index + p.length <= from) currnet_index += p.length;
}
// to set
for (self.pieces.items, edited_piece_index..) |p, i| {
if (currnet_index + p.length < to) {
var current_piece = &self.pieces.items[i];
try delete_actions.append(
alloc,
Action{
.piece_table_index = i,
.old_start = current_piece.start,
.old_length = current_piece.length,
}, },
); .delete => |deletes| {
try self.deleteWActionDisable(alloc, deletes.from, deletes.to, true);
current_piece.length = 0;
currnet_index += p.length;
}
if (currnet_index + p.length >= to) {
var current_piece = &self.pieces.items[i];
try delete_actions.append(
alloc,
Action{
.piece_table_index = i,
.old_start = current_piece.start,
.old_length = current_piece.length,
}, },
);
// shrink it
current_piece.start += to - currnet_index;
current_piece.length -= to - currnet_index;
break;
} }
} }
try self.actions.append(alloc, .{ .delete = delete_actions });
}
pub fn undo(self: *Self, alloc: std.mem.Allocator) !void { pub fn undo(self: *Self, alloc: std.mem.Allocator) !void {
var action_to_undo = self.actions.pop() orelse return; var action_group = self.actions.undo() orelse return;
switch (action_group.undo_action) {
switch (action_to_undo) {
.append => { .append => {
self.pieces.deinit(alloc); self.pieces.deinit(alloc);
self.pieces = .empty; self.pieces = .empty;
}, },
.insert => |insert_index| { .insert => |insert_index| {
_ = self.pieces.orderedRemove(insert_index);
// for the small price of one alloc you too can just swap nothing in
// might be faster, dont know should test
// [TODO] test if this is faster
try self.pieces.append(alloc, .none);
_ = self.pieces.swapRemove(insert_index);
}, },
.insert_splits_piece => |action| { .insert_splits_piece => |action| {
@@ -258,7 +329,7 @@ const PieceTable = struct {
piece.start = action.old_start; piece.start = action.old_start;
} }
action_to_undo.delete.deinit(alloc); action_group.undo_action.delete.deinit(alloc);
}, },
} }
} }
@@ -315,5 +386,33 @@ test "PieceTable insert" {
std.debug.print("{f}\n", .{piece_table}); std.debug.print("{f}\n", .{piece_table});
try piece_table.redo(std.testing.allocator);
std.debug.print("{f}\n", .{piece_table});
for (0..5) |_| {
try piece_table.insert(std.testing.allocator, .{
.text = &"a",
.start = 0,
.length = 1,
}, 20);
}
std.debug.print("{f}\n", .{piece_table});
try piece_table.undo(std.testing.allocator);
std.debug.print("{f}\n", .{piece_table});
try piece_table.undo(std.testing.allocator);
std.debug.print("{f}\n", .{piece_table});
try piece_table.undo(std.testing.allocator);
std.debug.print("{f}\n", .{piece_table});
try piece_table.undo(std.testing.allocator);
std.debug.print("{f}\n", .{piece_table});
try piece_table.undo(std.testing.allocator);
std.debug.print("{f}\n", .{piece_table});
try piece_table.undo(std.testing.allocator);
std.debug.print("{f}\n", .{piece_table});
// try piece_table.undo(std.testing.allocator);
// std.debug.print("{f}\n", .{piece_table});
piece_table.deinit(std.testing.allocator); piece_table.deinit(std.testing.allocator);
} }