Render active panels on narrow terminal

This commit is contained in:
slhx agent
2026-06-21 02:36:41 +02:00
parent 230f8f9129
commit ffa51e17c7
+99
View File
@@ -93,6 +93,10 @@ pub const Client = struct {
if (std.mem.startsWith(u8, line, "key ")) return self.handleTraceKey(line[4..]);
if (std.mem.startsWith(u8, line, "type ")) return self.handleInput(line[5..]);
if (std.mem.startsWith(u8, line, "panel_open ")) return self.applyProtocolCommand(line);
if (std.mem.eql(u8, line, "panel_close")) return self.applyProtocol("command panel_close");
if (std.mem.eql(u8, line, "panel_next")) return self.applyProtocol("command panel_next");
if (std.mem.eql(u8, line, "panel_prev")) return self.applyProtocol("command panel_prev");
if (std.mem.startsWith(u8, line, "open ")) return self.applyProtocol(line);
if (std.mem.startsWith(u8, line, "insert ")) return self.applyProtocolCommand(line);
if (std.mem.eql(u8, line, "left")) return self.applyProtocol("command move_left");
@@ -108,6 +112,11 @@ pub const Client = struct {
pub fn render(self: *const Client, allocator: std.mem.Allocator) ![]u8 {
try self.viewport.validate();
const snap = try self.session.snapshot();
if (snap.active_panel_title != null) return self.renderPanel(allocator, snap);
return self.renderEditor(allocator, snap);
}
fn renderEditor(self: *const Client, allocator: std.mem.Allocator, snap: session_mod.Snapshot) ![]u8 {
var out = std.ArrayList(u8).empty;
errdefer out.deinit(allocator);
@@ -151,6 +160,43 @@ pub const Client = struct {
return out.toOwnedSlice(allocator);
}
fn renderPanel(self: *const Client, allocator: std.mem.Allocator, snap: session_mod.Snapshot) ![]u8 {
var out = std.ArrayList(u8).empty;
errdefer out.deinit(allocator);
const max_body_lines = self.viewport.height - 1;
var body_lines_used: usize = 0;
const path = try self.session.panelPathAlloc(allocator);
defer allocator.free(path);
const header = try std.fmt.allocPrint(allocator, "panel {s}", .{path});
defer allocator.free(header);
try appendVisibleCells(allocator, &out, header, self.viewport.width);
try out.append(allocator, '\n');
body_lines_used += 1;
if (body_lines_used < max_body_lines) {
const title = snap.active_panel_title.?;
const detail = try std.fmt.allocPrint(allocator, "{s}: no content yet", .{title});
defer allocator.free(detail);
try appendVisibleCells(allocator, &out, detail, self.viewport.width);
try out.append(allocator, '\n');
body_lines_used += 1;
}
while (body_lines_used < max_body_lines) : (body_lines_used += 1) {
try out.append(allocator, '~');
try out.append(allocator, '\n');
}
const status = try std.fmt.allocPrint(
allocator,
"panel {d}/{d} x close",
.{ snap.active_panel_index.? + 1, snap.panel_depth },
);
defer allocator.free(status);
try appendVisibleCells(allocator, &out, status, self.viewport.width);
return out.toOwnedSlice(allocator);
}
pub fn handleInput(self: *Client, raw: []const u8) !void {
if (self.quit) return Error.ClientQuit;
const event = input.normalize(raw);
@@ -500,3 +546,56 @@ test "adversarial: unknown symbol key does not mutate the buffer" {
defer std.testing.allocator.free(frame);
try std.testing.expect(std.mem.indexOf(u8, frame, "unknown symbol key") != null);
}
test "regular: narrow terminal renders active panel instead of editor and returns cleanly" {
var client = try Client.init(std.testing.allocator, .{ .width = 20, .height = 4 });
defer client.deinit();
try client.handleTraceLine("open editor-text");
try client.handleTraceLine("panel_open files");
const frame = try client.render(std.testing.allocator);
defer std.testing.allocator.free(frame);
try std.testing.expect(std.mem.indexOf(u8, frame, "panel [files]") != null);
try std.testing.expect(std.mem.indexOf(u8, frame, "editor-text") == null);
try assertLinesFit(frame, 20);
try client.handleTraceLine("panel_close");
const editor_frame = try client.render(std.testing.allocator);
defer std.testing.allocator.free(editor_frame);
try std.testing.expect(std.mem.indexOf(u8, editor_frame, "editor-text") != null);
try std.testing.expect(std.mem.indexOf(u8, editor_frame, "panel [files]") == null);
try assertLinesFit(editor_frame, 20);
}
test "regular: nested panel breadcrumbs render on narrow terminal without overflow" {
var client = try Client.init(std.testing.allocator, .{ .width = 18, .height = 4 });
defer client.deinit();
try client.handleTraceLine("open abc");
try client.handleTraceLine("panel_open files");
try client.handleTraceLine("panel_open diagnostics");
const frame = try client.render(std.testing.allocator);
defer std.testing.allocator.free(frame);
try std.testing.expect(std.mem.indexOf(u8, frame, "panel files>") != null);
try std.testing.expect(std.mem.indexOf(u8, frame, "[diagnostics]") == null);
try assertLinesFit(frame, 18);
try client.handleTraceLine("panel_prev");
const previous_frame = try client.render(std.testing.allocator);
defer std.testing.allocator.free(previous_frame);
try std.testing.expect(std.mem.indexOf(u8, previous_frame, "panel [files]") != null);
try assertLinesFit(previous_frame, 18);
}
test "adversarial: invalid panel title and empty close recover without changing editor render" {
var client = try Client.init(std.testing.allocator, .{ .width = 16, .height = 4 });
defer client.deinit();
try client.handleTraceLine("open abc");
try std.testing.expectError(Error.ProtocolRejected, client.handleTraceLine("panel_open bad title"));
try std.testing.expectError(Error.ProtocolRejected, client.handleTraceLine("panel_close"));
const frame = try client.render(std.testing.allocator);
defer std.testing.allocator.free(frame);
try std.testing.expect(std.mem.indexOf(u8, frame, "abc") != null);
try assertLinesFit(frame, 16);
}