const std = @import("std"); const CommandIF = @import("./command/root.zig").Interface; /// const Self = @This(); /// const CommandList = std.ArrayList(CommandIF); /// commands: CommandList, /// next command to be applied by redoing head: usize, /// pub fn init(alloc: std.mem.Allocator) Self { return Self { .commands = CommandList.init(alloc), .head = 0, }; } /// pub fn deinit(self: *Self) void { for (self.commands.items) |*command| { command.deinit(); } self.commands.deinit(); } /// pub fn exec(self: *Self, command: CommandIF) !void { if (self.head >= self.commands.items.len) { try self.commands.append(command); } else { try self.commands.resize(self.head + 1); self.commands.items[self.head] = command; } errdefer _ = self.commands.pop(); self.head = self.commands.items.len; try command.apply(); } /// pub fn undo(self: *Self) !void { if (self.head == 0) { return error.NoCommand; } self.head -= 1; errdefer self.head += 1; try self.commands.items[self.head].revert(); } /// pub fn redo(self: *Self) !void { if (self.head >= self.commands.items.len) { return error.NoCommand; } try self.commands.items[self.head].apply(); self.head += 1; } /// pub fn isUndoAvailable(self: *const Self) bool { return self.head > 0; } /// pub fn isRedoAvailable(self: *const Self) bool { return self.head < self.commands.items.len; } test { var value1: i32 = 0; var value2: i32 = 0; var history = init(std.testing.allocator); defer history.deinit(); try std.testing.expectEqual(false, history.isUndoAvailable()); try std.testing.expectEqual(false, history.isRedoAvailable()); try std.testing.expectError(error.NoCommand, history.undo()); try std.testing.expectError(error.NoCommand, history.redo()); try history.exec( try CommandIF.make(std.testing.allocator, CommandIF.Mock { .target = &value1, })); try std.testing.expectEqual(history.isUndoAvailable(), true); try std.testing.expectEqual(history.isRedoAvailable(), false); try std.testing.expectEqual(value1, 1); try history.undo(); try std.testing.expectEqual(history.isUndoAvailable(), false); try std.testing.expectEqual(history.isRedoAvailable(), true); try std.testing.expectEqual(value1, 0); try history.redo(); try std.testing.expectEqual(history.isUndoAvailable(), true); try std.testing.expectEqual(history.isRedoAvailable(), false); try std.testing.expectEqual(value1, 1); try history.exec( try CommandIF.make(std.testing.allocator, CommandIF.Mock { .target = &value2, })); try std.testing.expectEqual(history.isUndoAvailable(), true); try std.testing.expectEqual(history.isRedoAvailable(), false); try std.testing.expectEqual(value1, 1); try std.testing.expectEqual(value2, 1); try history.undo(); try std.testing.expectEqual(history.isUndoAvailable(), true); try std.testing.expectEqual(history.isRedoAvailable(), true); try std.testing.expectEqual(value1, 1); try std.testing.expectEqual(value2, 0); try history.redo(); try std.testing.expectEqual(history.isUndoAvailable(), true); try std.testing.expectEqual(history.isRedoAvailable(), false); try std.testing.expectEqual(value1, 1); try std.testing.expectEqual(value2, 1); try history.undo(); try std.testing.expectEqual(history.isUndoAvailable(), true); try std.testing.expectEqual(history.isRedoAvailable(), true); try std.testing.expectEqual(value1, 1); try std.testing.expectEqual(value2, 0); try history.undo(); try std.testing.expectEqual(history.isUndoAvailable(), false); try std.testing.expectEqual(history.isRedoAvailable(), true); try std.testing.expectEqual(value1, 0); try std.testing.expectEqual(value2, 0); }