diff options
Diffstat (limited to 'src/main.zig')
| -rw-r--r-- | src/main.zig | 86 |
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| { |
