Add foreground Pi bridge panel

This commit is contained in:
slhx agent
2026-06-21 05:08:23 +02:00
parent 7170b7d14c
commit 73c65a9b6f
3 changed files with 203 additions and 0 deletions
+64
View File
@@ -1,8 +1,10 @@
const std = @import("std");
const context_mod = @import("context.zig");
const input = @import("input.zig");
const job_mod = @import("job.zig");
const leader_mod = @import("leader.zig");
const lsp_mod = @import("lsp.zig");
const pi_bridge = @import("pi_bridge.zig");
const protocol = @import("protocol.zig");
const replay = @import("replay.zig");
const repo_mod = @import("repo.zig");
@@ -164,6 +166,7 @@ pub const Client = struct {
if (std.mem.startsWith(u8, line, "lsp_signature ")) return self.openLspSignature(line[14..]);
if (std.mem.eql(u8, line, "lsp_param_next")) return self.moveLspParameter(.next);
if (std.mem.eql(u8, line, "lsp_param_previous")) return self.moveLspParameter(.previous);
if (std.mem.startsWith(u8, line, "pi_run ")) return self.openPiRun(line[7..]);
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_filter ")) return self.applyProtocolCommand(line);
@@ -537,6 +540,23 @@ pub const Client = struct {
self.message = null;
}
fn openPiRun(self: *Client, command_line: []const u8) !void {
const io = self.io orelse return Error.ProtocolRejected;
var argv = std.ArrayList([]const u8).empty;
defer argv.deinit(self.allocator);
var parts = std.mem.splitScalar(u8, std.mem.trim(u8, command_line, " "), ' ');
while (parts.next()) |part| {
if (part.len == 0) continue;
try argv.append(self.allocator, part);
}
const context_text = try context_mod.sessionContextAlloc(self.allocator, &self.session, "pi_run", "bridge", .{});
defer self.allocator.free(context_text);
const rows = pi_bridge.rowsAlloc(self.allocator, io, argv.items, context_text) catch return Error.ProtocolRejected;
defer freeOwnedRows(self.allocator, rows);
try self.session.openListPanel("pi-output", rows);
self.message = null;
}
fn jumpCurrentBufferToLspLocation(self: *Client, location: lsp_mod.DiagnosticLocation) !void {
const snap = try self.session.snapshot();
const bytes = try self.allocator.dupe(u8, snap.bytes);
@@ -1954,3 +1974,47 @@ test "adversarial: malformed hover signature and non-call parameter movement are
defer std.testing.allocator.free(frame);
try std.testing.expect(std.mem.indexOf(u8, frame, "lsp:param:outside_call") != null);
}
fn makeTuiPiFixture(allocator: std.mem.Allocator) !struct { tmp: std.testing.TmpDir, script: []u8 } {
var tmp = std.testing.tmpDir(.{});
errdefer tmp.cleanup();
try tmp.dir.writeFile(std.testing.io, .{ .sub_path = "fake-pi.sh", .data = "case \"$1\" in *context:v1*) echo pi_ok;; *) echo missing; exit 2;; esac\n" });
const script = try std.fmt.allocPrint(allocator, ".zig-cache/tmp/{s}/fake-pi.sh", .{&tmp.sub_path});
errdefer allocator.free(script);
return .{ .tmp = tmp, .script = script };
}
test "regular: pi bridge sends context to fake pi and renders output panel" {
var fixture = try makeTuiPiFixture(std.testing.allocator);
defer {
std.testing.allocator.free(fixture.script);
fixture.tmp.cleanup();
}
var client = try Client.initWithIo(std.testing.allocator, .{ .width = 80, .height = 7 }, std.testing.io);
defer client.deinit();
try client.handleTraceLine("open working buffer");
const command = try std.fmt.allocPrint(std.testing.allocator, "pi_run sh {s}", .{fixture.script});
defer std.testing.allocator.free(command);
try client.handleTraceLine(command);
const frame = try client.render(std.testing.allocator);
defer std.testing.allocator.free(frame);
try std.testing.expect(std.mem.indexOf(u8, frame, "pi-output") != null);
try std.testing.expect(std.mem.indexOf(u8, frame, "pi:context:bytes_") != null);
try std.testing.expect(std.mem.indexOf(u8, frame, "pi:status:exit_0") != null);
try std.testing.expect(std.mem.indexOf(u8, frame, "stdout:pi_ok") != null);
}
test "adversarial: missing pi command is visible and invalid command preserves buffer" {
var client = try Client.initWithIo(std.testing.allocator, .{ .width = 80, .height = 6 }, std.testing.io);
defer client.deinit();
try client.handleTraceLine("open safe");
try std.testing.expectError(Error.ProtocolRejected, client.handleTraceLine("pi_run "));
try client.handleTraceLine("pi_run definitely-not-a-mim-pi");
const frame = try client.render(std.testing.allocator);
defer std.testing.allocator.free(frame);
try std.testing.expect(std.mem.indexOf(u8, frame, "pi:status:spawn_error_") != null);
const snap = try client.session.snapshot();
try std.testing.expectEqualStrings("safe", snap.bytes);
}