summaryrefslogtreecommitdiff
path: root/src/selection_pipe.zig
diff options
context:
space:
mode:
authorGabriel Schneider <[email protected]>2026-10-01 17:43:39 -0300
committerGabriel Schneider <[email protected]>2026-10-01 20:04:42 -0300
commit805b9f188768fee523d462411d846912e0877ff3 (patch)
treedc17de5cee6e4b554446052a29fd55e8a5285498 /src/selection_pipe.zig
parent0728b937028418232a1da3d9922615a6f49680f2 (diff)
downloadpardes-805b9f188768fee523d462411d846912e0877ff3.tar.gz
pardes-805b9f188768fee523d462411d846912e0877ff3.zip
Edit runs acme's file and shell commands: < | > through the session's Shell off the loop (one undo step, a failure changes nothing, its stderr in +Errors, a 9P write answered when done), X and Y over the open text panes, b B D e r f w and the "file" address; checked against sam -d, and the reference's Edit section and Coming from acme row say so
Co-Authored-By: Claude Opus 5.5 <[email protected]>
Diffstat (limited to 'src/selection_pipe.zig')
-rw-r--r--src/selection_pipe.zig90
1 files changed, 79 insertions, 11 deletions
diff --git a/src/selection_pipe.zig b/src/selection_pipe.zig
index 44cc1709..4fb024bb 100644
--- a/src/selection_pipe.zig
+++ b/src/selection_pipe.zig
@@ -18,11 +18,18 @@ pub const command_timeout_seconds: u64 = 10;
pub const Input = struct { bytes: []const u8 };
/// Borrowed view exposed by the core while an effect is being drained.
+/// An Edit's `<`, `|` and `>` (src/edit_cmd.zig) run a command of their own
+/// per input, each in its file's directory: `commands` and `cwds`, when
+/// given, name input i's, and `shell` (the session's Shell) runs them,
+/// /bin/sh when it is empty.
pub const Request = struct {
id: u32,
command: []const u8,
cwd: []const u8,
inputs: []const Input,
+ commands: []const []const u8 = &.{},
+ cwds: []const []const u8 = &.{},
+ shell: []const u8 = "",
};
/// Worker-owned snapshot. `copy` is intentionally called synchronously while
@@ -33,6 +40,10 @@ pub const Job = struct {
command: []u8,
cwd: []u8,
inputs: [][]u8,
+ /// Input i's own command and directory, when the request gave them.
+ commands: [][]u8 = &.{},
+ cwds: [][]u8 = &.{},
+ shell: []u8 = &.{},
pub fn copy(gpa: std.mem.Allocator, request: Request) !*Job {
const job = try gpa.create(Job);
@@ -46,22 +57,45 @@ pub const Job = struct {
errdefer gpa.free(job.command);
job.cwd = try gpa.dupe(u8, filesystem.localPath(request.cwd) orelse request.cwd);
errdefer gpa.free(job.cwd);
- job.inputs = try gpa.alloc([]u8, request.inputs.len);
- errdefer gpa.free(job.inputs);
+ job.inputs = try dupeAll(gpa, request.inputs.len, request.inputs, false);
+ errdefer freeAll(gpa, job.inputs);
+ job.commands = try dupeAll(gpa, request.commands.len, request.commands, false);
+ errdefer freeAll(gpa, job.commands);
+ job.cwds = try dupeAll(gpa, request.cwds.len, request.cwds, true);
+ errdefer freeAll(gpa, job.cwds);
+ job.shell = try gpa.dupe(u8, request.shell);
+ return job;
+ }
+
+ /// Owned copies of `items` (Inputs or strings); a directory as the
+ /// host's own path, as `cwd` is.
+ fn dupeAll(gpa: std.mem.Allocator, n: usize, items: anytype, dirs: bool) ![][]u8 {
+ const out = try gpa.alloc([]u8, n);
var made: usize = 0;
- errdefer for (job.inputs[0..made]) |input| gpa.free(input);
- for (request.inputs, 0..) |input, i| {
- job.inputs[i] = try gpa.dupe(u8, input.bytes);
+ errdefer {
+ for (out[0..made]) |item| gpa.free(item);
+ gpa.free(out);
+ }
+ for (items) |item| {
+ const bytes: []const u8 = if (@TypeOf(item) == Input) item.bytes else item;
+ out[made] = try gpa.dupe(u8, if (dirs) filesystem.localPath(bytes) orelse bytes else bytes);
made += 1;
}
- return job;
+ return out;
+ }
+
+ fn freeAll(gpa: std.mem.Allocator, items: [][]u8) void {
+ for (items) |item| gpa.free(item);
+ gpa.free(items);
}
pub fn deinit(job: *Job, gpa: std.mem.Allocator) void {
gpa.free(job.command);
gpa.free(job.cwd);
- for (job.inputs) |input| gpa.free(input);
- gpa.free(job.inputs);
+ freeAll(gpa, job.inputs);
+ freeAll(gpa, job.commands);
+ freeAll(gpa, job.cwds);
+ gpa.free(job.shell);
gpa.destroy(job);
}
};
@@ -194,13 +228,26 @@ pub fn runOne(
cwd: []const u8,
input: []const u8,
) Outcome {
+ return runIn(gpa, io, "", command, cwd, input);
+}
+
+/// `runOne` through `shell -c` (/bin/sh when it is empty).
+pub fn runIn(
+ gpa: std.mem.Allocator,
+ io: std.Io,
+ shell: []const u8,
+ command: []const u8,
+ cwd: []const u8,
+ input: []const u8,
+) Outcome {
const fail = struct {
fn k(kind: Failure.Kind) Outcome {
return .{ .failed = .{ .kind = kind } };
}
};
if (std.mem.indexOfScalar(u8, command, 0) != null or
- std.mem.indexOfScalar(u8, cwd, 0) != null) return fail.k(.spawn);
+ std.mem.indexOfScalar(u8, cwd, 0) != null or
+ std.mem.indexOfScalar(u8, shell, 0) != null) return fail.k(.spawn);
// std's spawn runs no code in the child: this thread's mask, which the
// child inherits, is cleared across the fork (only the tty's SIGWINCH is
@@ -211,7 +258,7 @@ pub fn runOne(
std.posix.sigprocmask(std.posix.SIG.SETMASK, &none, &kept);
defer std.posix.sigprocmask(std.posix.SIG.SETMASK, &kept, null);
var child = std.process.spawn(io, .{
- .argv = &.{ "/bin/sh", "-c", command },
+ .argv = &.{ if (shell.len == 0) "/bin/sh" else shell, "-c", command },
.cwd = if (cwd.len == 0) .inherit else .{ .path = cwd },
.stdin = .pipe,
.stdout = .pipe,
@@ -301,7 +348,9 @@ pub fn runJob(gpa: std.mem.Allocator, io: std.Io, job: *const Job) Response {
var made: usize = 0;
var total: usize = 0;
for (job.inputs, 0..) |input, i| {
- const output = switch (runOne(gpa, io, job.command, job.cwd, input)) {
+ const command = if (job.commands.len > 0) job.commands[i] else job.command;
+ const cwd = if (job.cwds.len > 0) job.cwds[i] else job.cwd;
+ const output = switch (runIn(gpa, io, job.shell, command, cwd, input)) {
.ok => |bytes| bytes,
.failed => |f| {
// WHICH selection, because with several cursors "it failed" is
@@ -400,3 +449,22 @@ test "native pipe runner preserves stdin/stdout bytes and reports how it failed"
.failed => return error.EarlyStdinCloseShouldNotFail,
}
}
+
+test "a request may give each input its own command and directory, run by the shell it names" {
+ const gpa = std.testing.allocator;
+ const job = try Job.copy(gpa, .{
+ .id = 3,
+ .command = "",
+ .cwd = "",
+ .inputs = &.{ .{ .bytes = "" }, .{ .bytes = "abc" } },
+ .commands = &.{ "pwd", "tr a-z A-Z" },
+ .cwds = &.{ "/tmp", "/" },
+ .shell = "/bin/sh",
+ });
+ defer job.deinit(gpa);
+ var response = runJob(gpa, std.testing.io, job);
+ defer response.deinit(gpa);
+ try std.testing.expect(response.success);
+ try std.testing.expectEqualStrings("/tmp\n", response.outputs[0]);
+ try std.testing.expectEqualStrings("ABC", response.outputs[1]);
+}