Add generic session panel stack
This commit is contained in:
+83
-5
@@ -1,4 +1,5 @@
|
||||
const std = @import("std");
|
||||
const panel_mod = @import("panel.zig");
|
||||
|
||||
// Headless editor state for the protocol-first core.
|
||||
// req: coding/001, session/003
|
||||
@@ -35,6 +36,9 @@ pub const Snapshot = struct {
|
||||
cursor_byte: usize,
|
||||
cursor_cell: usize,
|
||||
selection: ?Selection,
|
||||
panel_depth: usize,
|
||||
active_panel_index: ?usize,
|
||||
active_panel_title: ?[]const u8,
|
||||
};
|
||||
|
||||
pub const Buffer = struct {
|
||||
@@ -60,6 +64,9 @@ pub const Buffer = struct {
|
||||
.cursor_byte = self.cursor.byte,
|
||||
.cursor_cell = self.cursor.cell,
|
||||
.selection = self.selection,
|
||||
.panel_depth = 0,
|
||||
.active_panel_index = null,
|
||||
.active_panel_title = null,
|
||||
};
|
||||
}
|
||||
|
||||
@@ -118,13 +125,15 @@ pub const Buffer = struct {
|
||||
pub const Session = struct {
|
||||
allocator: std.mem.Allocator,
|
||||
buffer: ?Buffer = null,
|
||||
panels: panel_mod.Stack,
|
||||
|
||||
pub fn init(allocator: std.mem.Allocator) Session {
|
||||
return .{ .allocator = allocator };
|
||||
return .{ .allocator = allocator, .panels = panel_mod.Stack.init(allocator) };
|
||||
}
|
||||
|
||||
pub fn deinit(self: *Session) void {
|
||||
if (self.buffer) |*buffer| buffer.deinit();
|
||||
self.panels.deinit();
|
||||
self.* = undefined;
|
||||
}
|
||||
|
||||
@@ -138,9 +147,44 @@ pub const Session = struct {
|
||||
return error.NoBufferOpen;
|
||||
}
|
||||
|
||||
pub fn openPanel(self: *Session, title: []const u8) !void {
|
||||
try self.panels.open(title);
|
||||
}
|
||||
|
||||
pub fn closePanel(self: *Session) !void {
|
||||
try self.panels.closeActive();
|
||||
}
|
||||
|
||||
pub fn nextPanel(self: *Session) !void {
|
||||
try self.panels.next();
|
||||
}
|
||||
|
||||
pub fn previousPanel(self: *Session) !void {
|
||||
try self.panels.previous();
|
||||
}
|
||||
|
||||
pub fn panelPathAlloc(self: *const Session, allocator: std.mem.Allocator) ![]u8 {
|
||||
return self.panels.pathAlloc(allocator);
|
||||
}
|
||||
|
||||
pub fn snapshot(self: *const Session) !Snapshot {
|
||||
if (self.buffer) |*buffer| return buffer.snapshot();
|
||||
return error.NoBufferOpen;
|
||||
const panel_state = self.panels.state();
|
||||
if (self.buffer) |*buffer| {
|
||||
var snap = buffer.snapshot();
|
||||
snap.panel_depth = panel_state.depth;
|
||||
snap.active_panel_index = panel_state.active_index;
|
||||
snap.active_panel_title = panel_state.active_title;
|
||||
return snap;
|
||||
}
|
||||
return .{
|
||||
.bytes = "",
|
||||
.cursor_byte = 0,
|
||||
.cursor_cell = 0,
|
||||
.selection = null,
|
||||
.panel_depth = panel_state.depth,
|
||||
.active_panel_index = panel_state.active_index,
|
||||
.active_panel_title = panel_state.active_title,
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
@@ -275,12 +319,14 @@ test "adversarial: invalid inserted UTF-8 is rejected and existing bytes are pre
|
||||
try std.testing.expectEqual(@as(usize, 0), buffer.snapshot().cursor_byte);
|
||||
}
|
||||
|
||||
test "adversarial: dispatch requires an open buffer" {
|
||||
test "adversarial: dispatch requires an open buffer but state remains inspectable" {
|
||||
var session = Session.init(std.testing.allocator);
|
||||
defer session.deinit();
|
||||
|
||||
try std.testing.expectError(error.NoBufferOpen, session.dispatch(.move_right));
|
||||
try std.testing.expectError(error.NoBufferOpen, session.snapshot());
|
||||
const snap = try session.snapshot();
|
||||
try std.testing.expectEqual(@as(usize, 0), snap.bytes.len);
|
||||
try std.testing.expectEqual(@as(usize, 0), snap.panel_depth);
|
||||
}
|
||||
|
||||
test "regular: pair insertion places cursor between delimiters" {
|
||||
@@ -308,3 +354,35 @@ test "adversarial: invalid UTF-8 pair insertion is rejected and existing bytes a
|
||||
try std.testing.expectEqualStrings("safe", buffer.snapshot().bytes);
|
||||
try std.testing.expectEqual(@as(usize, 0), buffer.snapshot().cursor_byte);
|
||||
}
|
||||
|
||||
test "regular: session opens closes and switches generic panel stack" {
|
||||
var session = Session.init(std.testing.allocator);
|
||||
defer session.deinit();
|
||||
|
||||
try session.openPanel("files");
|
||||
try session.openPanel("diagnostics");
|
||||
try session.previousPanel();
|
||||
var snap = try session.snapshot();
|
||||
try std.testing.expectEqual(@as(usize, 2), snap.panel_depth);
|
||||
try std.testing.expectEqualStrings("files", snap.active_panel_title.?);
|
||||
|
||||
try session.closePanel();
|
||||
snap = try session.snapshot();
|
||||
try std.testing.expectEqual(@as(usize, 1), snap.panel_depth);
|
||||
try std.testing.expectEqualStrings("diagnostics", snap.active_panel_title.?);
|
||||
|
||||
const path = try session.panelPathAlloc(std.testing.allocator);
|
||||
defer std.testing.allocator.free(path);
|
||||
try std.testing.expectEqualStrings("[diagnostics]", path);
|
||||
}
|
||||
|
||||
test "adversarial: session panel operations fail clearly on empty or invalid state" {
|
||||
var session = Session.init(std.testing.allocator);
|
||||
defer session.deinit();
|
||||
|
||||
try std.testing.expectError(error.NoPanelOpen, session.closePanel());
|
||||
try std.testing.expectError(error.NoPanelOpen, session.nextPanel());
|
||||
try std.testing.expectError(error.InvalidPanelTitle, session.openPanel("bad title"));
|
||||
const snap = try session.snapshot();
|
||||
try std.testing.expectEqual(@as(usize, 0), snap.panel_depth);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user