heavens-net/src/hncore/CommandHistory.zig

135 lines
3.8 KiB
Zig

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);
}