Add generic list panel primitive
This commit is contained in:
+119
-11
@@ -31,6 +31,12 @@ fn commandResponse(allocator: std.mem.Allocator, session: *session_mod.Session,
|
||||
if (std.mem.eql(u8, command, "delete_backward")) return dispatchAndRespond(allocator, session, .delete_backward);
|
||||
if (std.mem.startsWith(u8, command, "insert ")) return dispatchAndRespond(allocator, session, .{ .insert = command[7..] });
|
||||
if (std.mem.startsWith(u8, command, "panel_open ")) return panelRespond(allocator, session, .{ .open = command[11..] });
|
||||
if (std.mem.startsWith(u8, command, "list_open ")) return listOpenRespond(allocator, session, command[10..]);
|
||||
if (std.mem.startsWith(u8, command, "list_filter ")) return listRespond(allocator, session, .{ .filter = command[12..] });
|
||||
if (std.mem.eql(u8, command, "list_down")) return listRespond(allocator, session, .down);
|
||||
if (std.mem.eql(u8, command, "list_up")) return listRespond(allocator, session, .up);
|
||||
if (std.mem.eql(u8, command, "list_select")) return listRespond(allocator, session, .select);
|
||||
if (std.mem.eql(u8, command, "list_cancel")) return listRespond(allocator, session, .cancel);
|
||||
if (std.mem.eql(u8, command, "panel_close")) return panelRespond(allocator, session, .close);
|
||||
if (std.mem.eql(u8, command, "panel_next")) return panelRespond(allocator, session, .next);
|
||||
if (std.mem.eql(u8, command, "panel_prev")) return panelRespond(allocator, session, .previous);
|
||||
@@ -89,13 +95,61 @@ fn panelRespond(allocator: std.mem.Allocator, session: *session_mod.Session, com
|
||||
return stateResponse(allocator, session);
|
||||
}
|
||||
|
||||
const ListCommand = union(enum) {
|
||||
filter: []const u8,
|
||||
down,
|
||||
up,
|
||||
select,
|
||||
cancel,
|
||||
};
|
||||
|
||||
fn listOpenRespond(allocator: std.mem.Allocator, session: *session_mod.Session, payload: []const u8) ![]u8 {
|
||||
const split = std.mem.indexOfScalar(u8, payload, ' ') orelse return allocator.dupe(u8, "err invalid list open\n");
|
||||
const title = payload[0..split];
|
||||
const items_payload = payload[split + 1 ..];
|
||||
var items = std.ArrayList([]const u8).empty;
|
||||
defer items.deinit(allocator);
|
||||
var iter = std.mem.splitScalar(u8, items_payload, '|');
|
||||
while (iter.next()) |item| try items.append(allocator, item);
|
||||
session.openListPanel(title, items.items) catch |err| switch (err) {
|
||||
error.InvalidPanelTitle => return allocator.dupe(u8, "err invalid panel title\n"),
|
||||
error.InvalidListItem => return allocator.dupe(u8, "err invalid list item\n"),
|
||||
error.EmptyList => return allocator.dupe(u8, "err empty list\n"),
|
||||
else => return err,
|
||||
};
|
||||
return stateResponse(allocator, session);
|
||||
}
|
||||
|
||||
fn listRespond(allocator: std.mem.Allocator, session: *session_mod.Session, command: ListCommand) ![]u8 {
|
||||
switch (command) {
|
||||
.filter => |filter| session.filterListPanel(filter) catch |err| return listError(allocator, err),
|
||||
.down => session.listPanelDown() catch |err| return listError(allocator, err),
|
||||
.up => session.listPanelUp() catch |err| return listError(allocator, err),
|
||||
.select => _ = session.selectListPanel() catch |err| return listError(allocator, err),
|
||||
.cancel => session.cancelListPanel() catch |err| return listError(allocator, err),
|
||||
}
|
||||
return stateResponse(allocator, session);
|
||||
}
|
||||
|
||||
fn listError(allocator: std.mem.Allocator, err: anyerror) ![]u8 {
|
||||
return switch (err) {
|
||||
error.NoPanelOpen => allocator.dupe(u8, "err no panel open\n"),
|
||||
error.ActivePanelIsNotList => allocator.dupe(u8, "err active panel is not list\n"),
|
||||
error.InvalidListFilter => allocator.dupe(u8, "err invalid list filter\n"),
|
||||
error.EmptyList => allocator.dupe(u8, "err empty list\n"),
|
||||
else => err,
|
||||
};
|
||||
}
|
||||
|
||||
fn stateResponse(allocator: std.mem.Allocator, session: *session_mod.Session) ![]u8 {
|
||||
const snap = try session.snapshot();
|
||||
const panel_path = try session.panelPathAlloc(allocator);
|
||||
defer allocator.free(panel_path);
|
||||
const panel_summary = try session.panelSummaryAlloc(allocator);
|
||||
defer allocator.free(panel_summary);
|
||||
return std.fmt.allocPrint(
|
||||
allocator,
|
||||
"ok state cursor_byte={d} cursor_cell={d} bytes_len={d} panel_depth={d} active_panel={s} panel_path={s}\n",
|
||||
"ok state cursor_byte={d} cursor_cell={d} bytes_len={d} panel_depth={d} active_panel={s} panel_path={s} panel_summary={s}\n",
|
||||
.{
|
||||
snap.cursor_byte,
|
||||
snap.cursor_cell,
|
||||
@@ -103,6 +157,7 @@ fn stateResponse(allocator: std.mem.Allocator, session: *session_mod.Session) ![
|
||||
snap.panel_depth,
|
||||
if (snap.active_panel_title) |title| title else "-",
|
||||
panel_path,
|
||||
panel_summary,
|
||||
},
|
||||
);
|
||||
}
|
||||
@@ -113,15 +168,15 @@ test "regular: protocol opens bytes, reports state, and dispatches commands" {
|
||||
|
||||
const open = try handleLine(std.testing.allocator, &session, "open café\n");
|
||||
defer std.testing.allocator.free(open);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=0 cursor_cell=0 bytes_len=5 panel_depth=0 active_panel=- panel_path=-\n", open);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=0 cursor_cell=0 bytes_len=5 panel_depth=0 active_panel=- panel_path=- panel_summary=-\n", open);
|
||||
|
||||
const moved = try handleLine(std.testing.allocator, &session, "command move_right\n");
|
||||
defer std.testing.allocator.free(moved);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=1 cursor_cell=1 bytes_len=5 panel_depth=0 active_panel=- panel_path=-\n", moved);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=1 cursor_cell=1 bytes_len=5 panel_depth=0 active_panel=- panel_path=- panel_summary=-\n", moved);
|
||||
|
||||
const inserted = try handleLine(std.testing.allocator, &session, "command insert 🔥\n");
|
||||
defer std.testing.allocator.free(inserted);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=5 cursor_cell=3 bytes_len=9 panel_depth=0 active_panel=- panel_path=-\n", inserted);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=5 cursor_cell=3 bytes_len=9 panel_depth=0 active_panel=- panel_path=- panel_summary=-\n", inserted);
|
||||
}
|
||||
|
||||
test "regular: protocol delete command removes a whole UTF-8 codepoint" {
|
||||
@@ -137,7 +192,7 @@ test "regular: protocol delete command removes a whole UTF-8 codepoint" {
|
||||
response = try handleLine(std.testing.allocator, &session, "command delete_backward\n");
|
||||
defer std.testing.allocator.free(response);
|
||||
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=1 cursor_cell=1 bytes_len=1 panel_depth=0 active_panel=- panel_path=-\n", response);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=1 cursor_cell=1 bytes_len=1 panel_depth=0 active_panel=- panel_path=- panel_summary=-\n", response);
|
||||
}
|
||||
|
||||
test "adversarial: protocol rejects unknown requests and commands" {
|
||||
@@ -160,7 +215,7 @@ test "adversarial: protocol reports no buffer and invalid utf8 without corruptin
|
||||
|
||||
const no_buffer = try handleLine(std.testing.allocator, &session, "state\n");
|
||||
defer std.testing.allocator.free(no_buffer);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=0 cursor_cell=0 bytes_len=0 panel_depth=0 active_panel=- panel_path=-\n", no_buffer);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=0 cursor_cell=0 bytes_len=0 panel_depth=0 active_panel=- panel_path=- panel_summary=-\n", no_buffer);
|
||||
|
||||
try session.openFixture("safe");
|
||||
const before = try session.snapshot();
|
||||
@@ -189,7 +244,7 @@ test "regular: protocol pair commands insert delimiters with cursor between them
|
||||
response = try handleLine(std.testing.allocator, &session, "command pair parens\n");
|
||||
defer std.testing.allocator.free(response);
|
||||
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=5 cursor_cell=5 bytes_len=6 panel_depth=0 active_panel=- panel_path=-\n", response);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=5 cursor_cell=5 bytes_len=6 panel_depth=0 active_panel=- panel_path=- panel_summary=-\n", response);
|
||||
try std.testing.expectEqualStrings("call()", (try session.snapshot()).bytes);
|
||||
}
|
||||
|
||||
@@ -211,22 +266,22 @@ test "regular: protocol opens switches and closes generic panels" {
|
||||
{
|
||||
const response = try handleLine(std.testing.allocator, &session, "command panel_open files\n");
|
||||
defer std.testing.allocator.free(response);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=0 cursor_cell=0 bytes_len=0 panel_depth=1 active_panel=files panel_path=[files]\n", response);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=0 cursor_cell=0 bytes_len=0 panel_depth=1 active_panel=files panel_path=[files] panel_summary=empty\n", response);
|
||||
}
|
||||
{
|
||||
const response = try handleLine(std.testing.allocator, &session, "command panel_open diagnostics\n");
|
||||
defer std.testing.allocator.free(response);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=0 cursor_cell=0 bytes_len=0 panel_depth=2 active_panel=diagnostics panel_path=files>[diagnostics]\n", response);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=0 cursor_cell=0 bytes_len=0 panel_depth=2 active_panel=diagnostics panel_path=files>[diagnostics] panel_summary=empty\n", response);
|
||||
}
|
||||
{
|
||||
const response = try handleLine(std.testing.allocator, &session, "command panel_prev\n");
|
||||
defer std.testing.allocator.free(response);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=0 cursor_cell=0 bytes_len=0 panel_depth=2 active_panel=files panel_path=[files]>diagnostics\n", response);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=0 cursor_cell=0 bytes_len=0 panel_depth=2 active_panel=files panel_path=[files]>diagnostics panel_summary=empty\n", response);
|
||||
}
|
||||
{
|
||||
const response = try handleLine(std.testing.allocator, &session, "command panel_close\n");
|
||||
defer std.testing.allocator.free(response);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=0 cursor_cell=0 bytes_len=0 panel_depth=1 active_panel=diagnostics panel_path=[diagnostics]\n", response);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=0 cursor_cell=0 bytes_len=0 panel_depth=1 active_panel=diagnostics panel_path=[diagnostics] panel_summary=empty\n", response);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -245,3 +300,56 @@ test "adversarial: protocol rejects invalid panel titles and empty panel actions
|
||||
try std.testing.expectEqualStrings("err no panel open\n", response);
|
||||
}
|
||||
}
|
||||
|
||||
test "regular: protocol list panel filters moves selects and remains inspectable" {
|
||||
var session = session_mod.Session.init(std.testing.allocator);
|
||||
defer session.deinit();
|
||||
|
||||
{
|
||||
const response = try handleLine(std.testing.allocator, &session, "command list_open files src/main.zig|src/panel.zig|README.md\n");
|
||||
defer std.testing.allocator.free(response);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=0 cursor_cell=0 bytes_len=0 panel_depth=1 active_panel=files panel_path=[files] panel_summary=list:filter=-,cursor=0,visible=3,selected=-\n", response);
|
||||
}
|
||||
{
|
||||
const response = try handleLine(std.testing.allocator, &session, "command list_filter src\n");
|
||||
defer std.testing.allocator.free(response);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=0 cursor_cell=0 bytes_len=0 panel_depth=1 active_panel=files panel_path=[files] panel_summary=list:filter=src,cursor=0,visible=2,selected=-\n", response);
|
||||
}
|
||||
{
|
||||
const response = try handleLine(std.testing.allocator, &session, "command list_down\n");
|
||||
defer std.testing.allocator.free(response);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=0 cursor_cell=0 bytes_len=0 panel_depth=1 active_panel=files panel_path=[files] panel_summary=list:filter=src,cursor=1,visible=2,selected=-\n", response);
|
||||
}
|
||||
{
|
||||
const response = try handleLine(std.testing.allocator, &session, "command list_select\n");
|
||||
defer std.testing.allocator.free(response);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=0 cursor_cell=0 bytes_len=0 panel_depth=1 active_panel=files panel_path=[files] panel_summary=list:filter=src,cursor=1,visible=2,selected=src/panel.zig\n", response);
|
||||
}
|
||||
}
|
||||
|
||||
test "regular: protocol list cancel closes list panel" {
|
||||
var session = session_mod.Session.init(std.testing.allocator);
|
||||
defer session.deinit();
|
||||
|
||||
var response = try handleLine(std.testing.allocator, &session, "command list_open files a.zig|b.zig\n");
|
||||
std.testing.allocator.free(response);
|
||||
response = try handleLine(std.testing.allocator, &session, "command list_cancel\n");
|
||||
defer std.testing.allocator.free(response);
|
||||
try std.testing.expectEqualStrings("ok state cursor_byte=0 cursor_cell=0 bytes_len=0 panel_depth=0 active_panel=- panel_path=- panel_summary=-\n", response);
|
||||
}
|
||||
|
||||
test "adversarial: protocol list errors are explicit" {
|
||||
var session = session_mod.Session.init(std.testing.allocator);
|
||||
defer session.deinit();
|
||||
|
||||
{
|
||||
const response = try handleLine(std.testing.allocator, &session, "command list_open files bad item\n");
|
||||
defer std.testing.allocator.free(response);
|
||||
try std.testing.expectEqualStrings("err invalid list item\n", response);
|
||||
}
|
||||
{
|
||||
const response = try handleLine(std.testing.allocator, &session, "command list_filter src\n");
|
||||
defer std.testing.allocator.free(response);
|
||||
try std.testing.expectEqualStrings("err no panel open\n", response);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user