Show recoverable panel and editor feedback
This commit is contained in:
+79
-7
@@ -65,6 +65,7 @@ pub const Client = struct {
|
|||||||
leader: leader_mod.Leader,
|
leader: leader_mod.Leader,
|
||||||
viewport: Viewport,
|
viewport: Viewport,
|
||||||
saved_bytes: ?[]u8 = null,
|
saved_bytes: ?[]u8 = null,
|
||||||
|
message: ?[]const u8 = null,
|
||||||
quit: bool = false,
|
quit: bool = false,
|
||||||
|
|
||||||
pub fn init(allocator: std.mem.Allocator, viewport: Viewport) !Client {
|
pub fn init(allocator: std.mem.Allocator, viewport: Viewport) !Client {
|
||||||
@@ -96,6 +97,10 @@ pub const Client = struct {
|
|||||||
if (std.mem.startsWith(u8, line, "panel_open ")) return self.applyProtocolCommand(line);
|
if (std.mem.startsWith(u8, line, "panel_open ")) return self.applyProtocolCommand(line);
|
||||||
if (std.mem.startsWith(u8, line, "list_open ")) return self.applyProtocolCommand(line);
|
if (std.mem.startsWith(u8, line, "list_open ")) return self.applyProtocolCommand(line);
|
||||||
if (std.mem.startsWith(u8, line, "list_filter ")) return self.applyProtocolCommand(line);
|
if (std.mem.startsWith(u8, line, "list_filter ")) return self.applyProtocolCommand(line);
|
||||||
|
if (std.mem.startsWith(u8, line, "message ")) {
|
||||||
|
self.message = line[8..];
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (std.mem.eql(u8, line, "list_down")) return self.applyProtocol("command list_down");
|
if (std.mem.eql(u8, line, "list_down")) return self.applyProtocol("command list_down");
|
||||||
if (std.mem.eql(u8, line, "list_up")) return self.applyProtocol("command list_up");
|
if (std.mem.eql(u8, line, "list_up")) return self.applyProtocol("command list_up");
|
||||||
if (std.mem.eql(u8, line, "list_select")) return self.applyProtocol("command list_select");
|
if (std.mem.eql(u8, line, "list_select")) return self.applyProtocol("command list_select");
|
||||||
@@ -149,7 +154,9 @@ pub const Client = struct {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const leader_status = self.leader.status();
|
const leader_status = self.leader.status();
|
||||||
const status = if (leader_status.len != 0)
|
const status = if (self.message) |message|
|
||||||
|
try std.fmt.allocPrint(allocator, "{s}", .{message})
|
||||||
|
else if (leader_status.len != 0)
|
||||||
try std.fmt.allocPrint(
|
try std.fmt.allocPrint(
|
||||||
allocator,
|
allocator,
|
||||||
"{s}{s}{s}",
|
"{s}{s}{s}",
|
||||||
@@ -206,11 +213,14 @@ pub const Client = struct {
|
|||||||
try out.append(allocator, '\n');
|
try out.append(allocator, '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
const status = try std.fmt.allocPrint(
|
const status = if (self.message) |message|
|
||||||
allocator,
|
try std.fmt.allocPrint(allocator, "{s}", .{message})
|
||||||
"panel {d}/{d} x close",
|
else
|
||||||
.{ snap.active_panel_index.? + 1, snap.panel_depth },
|
try std.fmt.allocPrint(
|
||||||
);
|
allocator,
|
||||||
|
"panel {d}/{d} x close",
|
||||||
|
.{ snap.active_panel_index.? + 1, snap.panel_depth },
|
||||||
|
);
|
||||||
defer allocator.free(status);
|
defer allocator.free(status);
|
||||||
try appendVisibleCells(allocator, &out, status, self.viewport.width);
|
try appendVisibleCells(allocator, &out, status, self.viewport.width);
|
||||||
return out.toOwnedSlice(allocator);
|
return out.toOwnedSlice(allocator);
|
||||||
@@ -293,7 +303,19 @@ pub const Client = struct {
|
|||||||
fn applyProtocol(self: *Client, line: []const u8) !void {
|
fn applyProtocol(self: *Client, line: []const u8) !void {
|
||||||
const response = try protocol.handleLine(self.allocator, &self.session, line);
|
const response = try protocol.handleLine(self.allocator, &self.session, line);
|
||||||
defer self.allocator.free(response);
|
defer self.allocator.free(response);
|
||||||
if (std.mem.startsWith(u8, response, "err ")) return Error.ProtocolRejected;
|
if (std.mem.startsWith(u8, response, "err ")) {
|
||||||
|
self.message = protocolErrorMessage(response);
|
||||||
|
return Error.ProtocolRejected;
|
||||||
|
}
|
||||||
|
self.message = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
fn protocolErrorMessage(response: []const u8) []const u8 {
|
||||||
|
if (std.mem.indexOf(u8, response, "invalid panel title") != null) return "error: invalid panel title";
|
||||||
|
if (std.mem.indexOf(u8, response, "no panel open") != null) return "error: no panel open";
|
||||||
|
if (std.mem.indexOf(u8, response, "invalid list") != null) return "error: invalid list";
|
||||||
|
if (std.mem.indexOf(u8, response, "active panel is not list") != null) return "error: active panel is not list";
|
||||||
|
return "error: command failed";
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resize(self: *Client, payload: []const u8) !void {
|
fn resize(self: *Client, payload: []const u8) !void {
|
||||||
@@ -310,6 +332,7 @@ pub const Client = struct {
|
|||||||
const copy = try self.allocator.dupe(u8, snap.bytes);
|
const copy = try self.allocator.dupe(u8, snap.bytes);
|
||||||
if (self.saved_bytes) |old| self.allocator.free(old);
|
if (self.saved_bytes) |old| self.allocator.free(old);
|
||||||
self.saved_bytes = copy;
|
self.saved_bytes = copy;
|
||||||
|
self.message = "saved";
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -665,3 +688,52 @@ test "adversarial: list no-match state and invalid actions recover" {
|
|||||||
try client.handleTraceLine("list_cancel");
|
try client.handleTraceLine("list_cancel");
|
||||||
try std.testing.expectError(Error.ProtocolRejected, client.handleTraceLine("list_down"));
|
try std.testing.expectError(Error.ProtocolRejected, client.handleTraceLine("list_down"));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
test "regular: save status appears near editor status line" {
|
||||||
|
var client = try Client.init(std.testing.allocator, .{ .width = 20, .height = 4 });
|
||||||
|
defer client.deinit();
|
||||||
|
|
||||||
|
try client.handleTraceLine("open abc");
|
||||||
|
try client.handleTraceLine("key space");
|
||||||
|
try client.handleTraceLine("key s");
|
||||||
|
const frame = try client.render(std.testing.allocator);
|
||||||
|
defer std.testing.allocator.free(frame);
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, frame, "saved") != null);
|
||||||
|
try assertLinesFit(frame, 20);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "regular: empty generic panel shows local empty state" {
|
||||||
|
var client = try Client.init(std.testing.allocator, .{ .width = 22, .height = 4 });
|
||||||
|
defer client.deinit();
|
||||||
|
|
||||||
|
try client.handleTraceLine("panel_open help");
|
||||||
|
const frame = try client.render(std.testing.allocator);
|
||||||
|
defer std.testing.allocator.free(frame);
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, frame, "help: no content yet") != null);
|
||||||
|
try assertLinesFit(frame, 22);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "adversarial: protocol errors render recoverable local message" {
|
||||||
|
var client = try Client.init(std.testing.allocator, .{ .width = 24, .height = 4 });
|
||||||
|
defer client.deinit();
|
||||||
|
|
||||||
|
try client.handleTraceLine("open abc");
|
||||||
|
try std.testing.expectError(Error.ProtocolRejected, client.handleTraceLine("panel_open bad title"));
|
||||||
|
const frame = try client.render(std.testing.allocator);
|
||||||
|
defer std.testing.allocator.free(frame);
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, frame, "error: invalid panel") != null);
|
||||||
|
try assertLinesFit(frame, 24);
|
||||||
|
}
|
||||||
|
|
||||||
|
test "adversarial: long feedback messages are clipped to viewport width" {
|
||||||
|
var client = try Client.init(std.testing.allocator, .{ .width = 16, .height = 4 });
|
||||||
|
defer client.deinit();
|
||||||
|
|
||||||
|
try client.handleTraceLine("open abc");
|
||||||
|
try client.handleTraceLine("message this-message-is-way-too-long-for-the-phone-width");
|
||||||
|
const frame = try client.render(std.testing.allocator);
|
||||||
|
defer std.testing.allocator.free(frame);
|
||||||
|
try assertLinesFit(frame, 16);
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, frame, "this-message-is-") != null);
|
||||||
|
try std.testing.expect(std.mem.indexOf(u8, frame, "phone-width") == null);
|
||||||
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user