From 317c8c3ad277e8b018f732d08c9ce1100de9f966 Mon Sep 17 00:00:00 2001 From: sirlilpanda Date: Wed, 17 Jun 2026 01:27:09 +1200 Subject: [PATCH] init --- README.md | 1 + src/root.zig | 303 ++++++++++++++++++++++++++++++++++----------------- 2 files changed, 202 insertions(+), 102 deletions(-) create mode 100644 README.md diff --git a/README.md b/README.md new file mode 100644 index 0000000..7a4a9f1 --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +piece table with both undo and redo features. \ No newline at end of file diff --git a/src/root.zig b/src/root.zig index d468cda..69b868d 100644 --- a/src/root.zig +++ b/src/root.zig @@ -38,19 +38,38 @@ const Action = struct { old_length: usize, }; -const ActionGroup = union(ActionType) { - /// this will only occur when the table was already empty so boof it - append: void, +const ActionGroup = struct { + const PieceRedo = struct { + piece: Piece, + index: usize, + }; - /// happens when a piece is cleanly inserted in to the table - insert: 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 + append: void, - /// happens when the piece requires to be split before indexing - insert_splits_piece: Action, + /// happens when a piece is cleanly inserted in to the table + insert: usize, - /// happens when a piece is deletes (length set to zero) - /// appends all modifed pieces to be restored - delete: std.ArrayList(Action), + /// happens when the piece requires to be split before indexing + insert_splits_piece: Action, + + /// happens when a piece is deletes (length set to zero) + /// appends all modifed pieces to be restored + 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 @@ -59,20 +78,36 @@ const ActionList = struct { actions: std.ArrayList(ActionGroup) = .empty, + /// offsets from the last index i down to zero + undo_offset: usize = 0, + pub const empty = ActionList{}; 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); } - pub fn pop(self: *Self) ?ActionGroup { - return self.actions.pop(); + pub fn undo(self: *Self) ?ActionGroup { + 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 { for (self.actions.items) |*act| { - switch (act.*) { - .delete => act.delete.deinit(alloc), + switch (act.undo_action) { + .delete => act.undo_action.delete.deinit(alloc), else => continue, } } @@ -103,6 +138,92 @@ const PieceTable = struct { /// piece : the piece to add /// 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 { + 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; for (self.pieces.items, 0..) |p, i| { @@ -126,18 +247,30 @@ const PieceTable = struct { }; try self.pieces.replaceRange(alloc, i, 1, &pieces); - try self.actions.append(alloc, .{ - .insert_splits_piece = Action{ - .piece_table_index = i, - .old_length = p.length, - .old_start = p.start, + if (!action_disabled) try self.actions.append(alloc, ActionGroup{ + .undo_action = .{ + .insert_splits_piece = Action{ + .piece_table_index = i, + .old_length = p.length, + .old_start = p.start, + }, }, + .redo_action = .{ .insert_splits_piece = .{ + .piece = piece, + .index = index, + } }, }); return; } if (currnet_start + p.length == index) { 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; } // just append after if (currnet_start + p.length < index) currnet_start += p.length; @@ -145,99 +278,37 @@ const PieceTable = struct { // if there are no items this runs 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) - /// 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 { - var currnet_index: usize = 0; - var edited_piece_index: usize = 0; - var delete_actions: std.ArrayList(Action) = .empty; + pub fn redo(self: *Self, alloc: std.mem.Allocator) !void { + const action_group = self.actions.redo() orelse return; - // 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]; - - 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; + switch (action_group.redo_action) { + .append => |piece| { + try self.insertWActionDisable(alloc, piece, 0, true); + }, + .insert, .insert_splits_piece => |action| { + try self.insertWActionDisable(alloc, action.piece, action.index, true); + }, + .delete => |deletes| { + try self.deleteWActionDisable(alloc, deletes.from, deletes.to, true); + }, } - - // 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, - }, - ); - - 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 { - var action_to_undo = self.actions.pop() orelse return; - - switch (action_to_undo) { + var action_group = self.actions.undo() orelse return; + switch (action_group.undo_action) { .append => { self.pieces.deinit(alloc); self.pieces = .empty; }, .insert => |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); + _ = self.pieces.orderedRemove(insert_index); }, .insert_splits_piece => |action| { @@ -258,7 +329,7 @@ const PieceTable = struct { 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}); + 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); }