summaryrefslogtreecommitdiff
path: root/src/main.zig
diff options
context:
space:
mode:
Diffstat (limited to 'src/main.zig')
-rw-r--r--src/main.zig86
1 files changed, 61 insertions, 25 deletions
diff --git a/src/main.zig b/src/main.zig
index 6bdf2bfd..101a5be3 100644
--- a/src/main.zig
+++ b/src/main.zig
@@ -102,13 +102,18 @@ const help_text =
/// pane `path` landed in (the one it already had, if open), then /log
/// followed on one connection until that pane's `del` -- exit 0 -- or the
/// connection ends with the session -- exit 1.
-fn waitForDel(io: std.Io, gpa: std.mem.Allocator, dial: []const u8, path: []const u8) noreturn {
- const first = ninep_io.Client.read(gpa, dial, "/index", "/index") catch std.process.exit(1);
- const serial = paneShowing(first, path) orelse {
- std.Io.File.stderr().writeStreamingAll(io, "pardes: --wait: no pane shows that file\n") catch {};
- std.process.exit(1);
+fn waitForDel(io: std.Io, gpa: std.mem.Allocator, dial: []const u8, path: []const u8, known: ?u32) noreturn {
+ _ = io;
+ // The pane this launch made (a name not there yet) is known by its
+ // serial; one a look opened or went to is found by its file. Found by
+ // none after the look went there, it was closed already: that is the
+ // end being waited for, not a failure (a pane closed between the look
+ // and this read made `--wait` exit 1 before).
+ const serial = known orelse found: {
+ const first = ninep_io.Client.read(gpa, dial, "/index", "/index") catch std.process.exit(1);
+ defer gpa.free(first);
+ break :found paneShowing(first, path) orelse std.process.exit(0);
};
- gpa.free(first);
const Wait = struct {
gpa: std.mem.Allocator,
dial: []const u8,
@@ -390,22 +395,35 @@ fn nativeMain(init: std.process.Init) !void {
// leave a stale environment variable able to lock someone out of their
// own editor.
const parent = found orelse break :forwarding;
+ // Does it answer? One that answers takes the file or says why not,
+ // as acme's B does: from here on never a nested editor, whose
+ // screen would be drawn inside the pane that asked.
+ ninep_io.Client.probe(arena, parent.dial) catch break :forwarding;
+ const Refuse = struct {
+ fn with(io: std.Io, what: []const u8, err: anyerror) noreturn {
+ const why = if (err == error.Remote) ninep_io.Client.lastSaid() else @errorName(err);
+ var buf: [8192]u8 = undefined;
+ const text = std.fmt.bufPrint(&buf, "pardes: {s}: {s}\n", .{ what[0..@min(what.len, 4096)], why }) catch "pardes: refused\n";
+ std.Io.File.stderr().writeStreamingAll(io, text) catch {};
+ std.process.exit(1);
+ }
+ };
// The pane's `look` file: one line, and the line is the clicked text
// itself, which is what a right click in that pane would have been.
var look_buf: [64]u8 = undefined;
const look = try std.fmt.bufPrint(&look_buf, "/pane/{d}/look", .{parent.serial});
- const word = positional orelse {
+ const typed_word = positional orelse {
var tag_buf: [64]u8 = undefined;
const tag = try std.fmt.bufPrint(&tag_buf, "/pane/{d}/tag", .{parent.serial});
- const contents = ninep_io.Client.read(arena, parent.dial, tag, tag) catch break :forwarding;
+ const contents = ninep_io.Client.read(arena, parent.dial, tag, tag) catch |err| Refuse.with(init.io, tag, err);
arena.free(contents);
try std.Io.File.stderr().writeStreamingAll(init.io, nested_text);
std.process.exit(1);
};
- // A word this launch cannot turn into a path is an argument problem
- // rather than a nesting one: fall through and let the session that
- // starts report it, the way `pardes typo` outside pardes does.
- if (std.mem.indexOfAny(u8, word, "\r\n") != null) break :forwarding;
+ if (std.mem.indexOfAny(u8, typed_word, "\r\n") != null) Refuse.with(init.io, "a file name", error.NotOneLine);
+ // `pardes '~/x'`, quoted so the shell left it: home, as a look's.
+ var home_buf: [4096]u8 = undefined;
+ const word = pardes.filesystem.expandHome(typed_word, &home_buf);
const target = @import("look.zig").parsePathLine(word);
var realbuf: [4096]u8 = undefined;
var newbuf: [4096]u8 = undefined;
@@ -415,32 +433,50 @@ fn nativeMain(init: std.process.Init) !void {
// resolved and the name kept, a pane made for it that Save
// creates the file from.
const base = std.fs.path.basename(target.path);
- if (base.len == 0 or std.mem.eql(u8, base, ".") or std.mem.eql(u8, base, "..")) break :forwarding;
+ if (base.len == 0 or std.mem.eql(u8, base, ".") or std.mem.eql(u8, base, "..")) Refuse.with(init.io, word, error.NotAFileName);
const dir = pardes.filesystem.resolveOs(std.fs.path.dirname(target.path) orelse ".", &realbuf) orelse {
// Its directory not there either: the name made absolute as
- // written, and the pane's Save makes the directories (a
- // name into a missing one says so, pane.zig writeName).
+ // written, and the pane's Save makes the directories.
var cwd_buf: [4096]u8 = undefined;
- if (std.c.getcwd(&cwd_buf, cwd_buf.len) == null) break :forwarding;
+ if (std.c.getcwd(&cwd_buf, cwd_buf.len) == null) Refuse.with(init.io, word, error.NoWorkingDirectory);
const cwd = std.mem.sliceTo(&cwd_buf, 0);
- break :named std.fs.path.resolvePosix(arena, &.{ cwd, target.path }) catch break :forwarding;
+ break :named std.fs.path.resolvePosix(arena, &.{ cwd, target.path }) catch |err| Refuse.with(init.io, word, err);
};
- break :named std.fmt.bufPrint(&newbuf, "{s}/{s}", .{ std.mem.trimEnd(u8, dir.path, "/"), base }) catch break :forwarding;
+ break :named std.fmt.bufPrint(&newbuf, "{s}/{s}", .{ std.mem.trimEnd(u8, dir.path, "/"), base }) catch |err| Refuse.with(init.io, word, err);
};
+ var made_serial: ?u32 = null;
if (found_path != null) {
var command_buf: [8192]u8 = undefined;
- const command = std.fmt.bufPrint(&command_buf, "{s}{s}\n", .{ path, word[target.path.len..] }) catch break :forwarding;
- ninep_io.Client.write(arena, parent.dial, look, command) catch break :forwarding;
+ const command = std.fmt.bufPrint(&command_buf, "{s}{s}\n", .{ path, word[target.path.len..] }) catch |err| Refuse.with(init.io, word, err);
+ ninep_io.Client.write(arena, parent.dial, look, command) catch |err| Refuse.with(init.io, word, err);
} else {
- const made = ninep_io.Client.read(arena, parent.dial, "/pane/new", "/pane/new") catch break :forwarding;
- const serial = std.fmt.parseInt(u32, std.mem.trim(u8, made, " \n"), 10) catch break :forwarding;
+ const made = ninep_io.Client.read(arena, parent.dial, "/pane/new", "/pane/new") catch |err| Refuse.with(init.io, word, err);
+ const serial = std.fmt.parseInt(u32, std.mem.trim(u8, made, " \n"), 10) catch |err| Refuse.with(init.io, word, err);
+ made_serial = serial;
var name_buf: [64]u8 = undefined;
const name = try std.fmt.bufPrint(&name_buf, "/pane/{d}/name", .{serial});
var line_buf: [4200]u8 = undefined;
- const line = std.fmt.bufPrint(&line_buf, "{s}\n", .{path}) catch break :forwarding;
- ninep_io.Client.write(arena, parent.dial, name, line) catch break :forwarding;
+ const line = std.fmt.bufPrint(&line_buf, "{s}\n", .{path}) catch |err| Refuse.with(init.io, word, err);
+ ninep_io.Client.write(arena, parent.dial, name, line) catch |err| {
+ // The name refused: the pane made for it goes too, no
+ // stray +New left behind.
+ var ctl_buf: [64]u8 = undefined;
+ const ctl = std.fmt.bufPrint(&ctl_buf, "/pane/{d}/ctl", .{serial}) catch unreachable;
+ var why_buf: [256]u8 = undefined;
+ const said = ninep_io.Client.lastSaid();
+ const why = why_buf[0..@min(said.len, why_buf.len)];
+ @memcpy(why, said[0..why.len]);
+ ninep_io.Client.write(arena, parent.dial, ctl, "delete\n") catch {};
+ if (err == error.Remote) {
+ var buf: [8192]u8 = undefined;
+ const text = std.fmt.bufPrint(&buf, "pardes: {s}: {s}\n", .{ word[0..@min(word.len, 4096)], why }) catch "pardes: refused\n";
+ std.Io.File.stderr().writeStreamingAll(init.io, text) catch {};
+ std.process.exit(1);
+ }
+ Refuse.with(init.io, word, err);
+ };
}
- if (wait) waitForDel(init.io, arena, parent.dial, path);
+ if (wait) waitForDel(init.io, arena, parent.dial, path, made_serial);
return;
}
if (positional) |a| {