From 9ef2f974232622872673ef71697a890e552642cf Mon Sep 17 00:00:00 2001 From: slhx agent Date: Sun, 21 Jun 2026 21:26:51 +0200 Subject: [PATCH] Expose file and Pi panels --- KEYMAP.md | 6 ++++-- src/leader.zig | 22 ++++++++++++++++++++-- src/tui.zig | 22 ++++++++++++++++++++++ tools/terminal_e2e.py | 1 + 4 files changed, 47 insertions(+), 4 deletions(-) diff --git a/KEYMAP.md b/KEYMAP.md index e193b1b..7f69e1f 100644 --- a/KEYMAP.md +++ b/KEYMAP.md @@ -266,7 +266,8 @@ Opening a result returns to the editor without leaving behind permanent splits. | Keys | Surface | | --- | --- | -| `Space f` | files: file tree/finder/recent | +| `Space f` | files panel / file tree / finder / recent | +| `Space P` | Pi/local trusted tool panel | | `Space s` | search: file/project/symbol | | `Space d` | diagnostics | | `Space g` | go/navigation | @@ -275,7 +276,8 @@ Opening a result returns to the editor without leaving behind permanent splits. | `Space b` | buffers/build output depending on rail label | | `Space v` | version control/git workbench | | `Space !` | terminal escape hatch/job command | -| `Space p` | Pi/local trusted tool context | +| `Space p` | coding symbol rail | +| `Space P` | Pi/local trusted tool panel | Panel controls are consistent: diff --git a/src/leader.zig b/src/leader.zig index f6f6540..1e0e719 100644 --- a/src/leader.zig +++ b/src/leader.zig @@ -26,6 +26,7 @@ pub const Action = union(enum) { open: []u8, symbol: symbol_mod.Symbol, file_picker, + pi_panel, search_file, search_project, hover, @@ -102,7 +103,7 @@ pub const Leader = struct { if (self.message) |message| return message; return switch (self.mode) { .idle => "", - .rail => "leader: w save q quit Q discard o open p symbols s search r repeat", + .rail => "leader: w save q quit Q discard f files P Pi o open p symbols s search r repeat", .symbol_rail => symbol_mod.rail_status, .search_rail => "search: f current file p project s symbols", .language_rail => "language: h hover s sig f fmt o imports a actions (Space path)", @@ -181,6 +182,10 @@ pub const Leader = struct { self.mode = .idle; return .file_picker; } + if (std.mem.eql(u8, text, "P")) { + self.mode = .idle; + return .pi_panel; + } if (std.mem.eql(u8, text, "p")) { self.mode = .symbol_rail; return .none; @@ -448,7 +453,7 @@ test "regular: space opens a visible leader rail and write dispatches" { defer leader.deinit(); try expectActionTag(.none, try leader.handleEvent(input.normalize(" "))); - try std.testing.expectEqualStrings("leader: w save q quit Q discard o open p symbols s search r repeat", leader.status()); + try std.testing.expectEqualStrings("leader: w save q quit Q discard f files P Pi o open p symbols s search r repeat", leader.status()); try expectActionTag(.save, try leader.handleEvent(input.normalize("w"))); try std.testing.expect(!leader.isActive()); @@ -576,3 +581,16 @@ test "adversarial: unknown and cancelled symbol rail inputs do not dispatch" { try expectActionTag(.none, try leader.handleEvent(input.normalize("\x1b"))); try std.testing.expectEqualStrings("symbol rail cancelled", leader.status()); } + +test "regular: leader exposes files and Pi panels" { + var leader = Leader.init(std.testing.allocator); + defer leader.deinit(); + + try expectActionTag(.none, try leader.handleEvent(input.normalize(" "))); + try std.testing.expect(std.mem.indexOf(u8, leader.status(), "f files") != null); + try std.testing.expect(std.mem.indexOf(u8, leader.status(), "P Pi") != null); + try expectActionTag(.file_picker, try leader.handleEvent(input.normalize("f"))); + + try expectActionTag(.none, try leader.handleEvent(input.normalize(" "))); + try expectActionTag(.pi_panel, try leader.handleEvent(input.normalize("P"))); +} diff --git a/src/tui.zig b/src/tui.zig index a83cba1..2692641 100644 --- a/src/tui.zig +++ b/src/tui.zig @@ -1313,6 +1313,15 @@ pub const Client = struct { self.panel_context = .file_picker; } + fn openPiPanel(self: *Client) !void { + const rows = [_][]const u8{ + "pi_ask:explain_current_buffer", + "pi_edit:propose_patch", + "pi_review:critique_diff", + }; + try self.openListRows("pi", rows[0..], .none); + } + fn openListRows(self: *Client, title: []const u8, rows: []const []const u8, context: PanelContext) !void { if (rows.len == 0) { self.message = "empty list"; @@ -2426,6 +2435,7 @@ pub const Client = struct { }, .symbol => |symbol| try self.applyProtocol(symbol_mod.protocolCommand(symbol)), .file_picker => try self.openFilePickerPanel(), + .pi_panel => try self.openPiPanel(), .search_file => self.openSearchPrompt(), .search_project => self.openProjectSearchPrompt(), .hover => try self.showCompactHover(), @@ -5353,3 +5363,15 @@ test "regular: dot repeats put and remains undoable" { snap = try client.session.snapshot(); try std.testing.expectEqualStrings("abcabc", snap.bytes); } + +test "regular: Space P opens Pi panel" { + var client = try Client.init(std.testing.allocator, .{ .width = 56, .height = 10 }); + defer client.deinit(); + + try client.handleInput(" "); + try client.handleInput("P"); + const frame = try client.render(std.testing.allocator); + defer std.testing.allocator.free(frame); + try std.testing.expect(std.mem.indexOf(u8, frame, "panel [pi]") != null); + try std.testing.expect(std.mem.indexOf(u8, frame, "pi_ask:explain_current_buffer") != null); +} diff --git a/tools/terminal_e2e.py b/tools/terminal_e2e.py index 558a825..d517f59 100755 --- a/tools/terminal_e2e.py +++ b/tools/terminal_e2e.py @@ -527,6 +527,7 @@ SCENARIOS = [ Scenario("multi-file-coding-loop", 72, 16, "repo-multifile", "attached-keyboard", "multi-file", "truecolor", "file", setup_multifile, (b"A", b"?", b"\x1b", b" ", b"w", b" ", b"o", b"r", b"e", b"p", b"o", b"/", b"a", b"l", b"p", b"h", b"a", b".", b"z", b"i", b"g", b"\r", b"A", b"!", b"\x1b", b" ", b"w", b" ", b"q"), None, ("alpha.zig", "beta", "☻", "mode:normal"), max_key_events=30, expect_files=(("alpha.zig", "alpha!\n"), ("beta.zig", "beta?"))), Scenario("lsp-assisted-coding", 60, 12, "lsp-fixture", "attached-keyboard", "lsp", "truecolor", "file", setup_lsp_assist, (b" ", b"l", b"h", b" ", b"l", b"s", b" ", b"d", b"q", b" ", b"d", b"n", b" ", b"l", b"f", b" ", b"w", b" ", b"q"), "ZLS", ("[zls] add(lhs, rhs)", "[zls] add(lhs: i32, rhs: i32) active=rhs", "diag:fresh:zls", "format:zls:applied", "ZLS", "mode:normal"), max_key_events=19, prelude=LSP_ASSIST_PRELUDE), Scenario("project-search-panel", 60, 12, "repo-search", "attached-keyboard", "project-search", "truecolor", "file", setup_project_search, (b" ", b"s", b"p", b"f", b"i", b"n", b"d", b"m", b"e", b"\r", b"q", b" ", b"q"), "main\n", ("panel [search]", "target.zig", "findme", "mode:normal"), max_key_events=13), + Scenario("file-and-pi-panels", 60, 12, "repo-multifile", "attached-keyboard", "panels", "truecolor", "file", setup_multifile, (b" ", b"f", b"q", b" ", b"P", b"q", b" ", b"q"), "beta\n", ("panel [files]", "alpha.zig", "panel [pi]", "pi_ask:explain_current_buffer", "mode:normal"), max_key_events=8), Scenario("job-cancel-output-panel", 60, 12, "job", "attached-keyboard", "job-output", "truecolor", "file", setup_empty, (b" ", b"t", b"x", b"q", b" ", b"q"), "", ("panel [job-output]", "job:profile:build", "job:status:cancelled:user", "mode:normal"), max_key_events=6), Scenario("dirty-discard-shift-q", 52, 12, "empty", "ios-default-qwertz-space-path", "dirty-discard", "truecolor", "file", setup_empty, tuple(bytes([b]) for b in b" ps Q"), "", ("1│", "☻")), Scenario("directory-panel-narrow", 52, 12, "directory", "ios-default-qwertz-space-path", "panel", "mono", "directory", setup_directory, PANEL_QUIT, None, ("file", "one.zig"), required_attrs=()),