summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/builtins.zig17
-rw-r--r--src/exec.zig21
-rw-r--r--src/host_io.zig25
-rw-r--r--src/pardes.zig25
4 files changed, 63 insertions, 25 deletions
diff --git a/src/builtins.zig b/src/builtins.zig
index fbab925c..751d4026 100644
--- a/src/builtins.zig
+++ b/src/builtins.zig
@@ -874,11 +874,18 @@ pub const Tty = struct {
pub const takes_arg = true;
pub const plus_arg = true;
pub fn run(c: Ctx) void {
- exec.spawnTty(c.p, c.id);
- const arg = c.arg orelse return;
- const pane = c.p.panes[c.p.active] orelse return;
- if (!pane.isTerminal() or pane.shell != null) return;
- pane.shell = c.p.gpa.dupe(u8, arg) catch return;
+ const arg = std.mem.trim(u8, c.arg orelse "", " \t");
+ // A shell that is not there is said, where the host would quietly
+ // start its fallback in its place.
+ if (arg.len > 0) {
+ var buf: [std.fs.max_path_bytes]u8 = undefined;
+ if (comptime pardes.hosted) if (@import("host_io.zig").Shell.find(arg, &buf) == null) {
+ var said: [320]u8 = undefined;
+ return c.p.reportFailure(c.id, std.fmt.bufPrint(&said, "Tty: no shell \"{s}\" (a name on the usual paths, or a path to one)", .{arg[0..@min(arg.len, 200)]}) catch "Tty: no such shell");
+ };
+ }
+ const pane = exec.spawnTty(c.p, c.id) orelse return;
+ if (arg.len > 0) pane.shell = c.p.gpa.dupe(u8, arg) catch null;
}
};
diff --git a/src/exec.zig b/src/exec.zig
index cc2d3c4f..dbda7217 100644
--- a/src/exec.zig
+++ b/src/exec.zig
@@ -292,8 +292,8 @@ fn askWrite(p: *Pardes, id: usize, serial: u32, path: []const u8) void {
/// Tty: a shell in the caller's directory, raw from the first frame,
/// stacked below the caller like Alt-n's new shell.
-pub fn spawnTty(p: *Pardes, from: usize) void {
- spawnTtyWithMount(p, from, false);
+pub fn spawnTty(p: *Pardes, from: usize) ?*Pane {
+ return spawnTtyWithMount(p, from, false);
}
pub fn spawnV9fsTty(p: *Pardes, from: usize) void {
@@ -301,13 +301,19 @@ pub fn spawnV9fsTty(p: *Pardes, from: usize) void {
return p.reportError(from, "Tty9p", error.LinuxRequired);
if (p.fs.socket_path.len == 0)
return p.reportError(from, "Tty9p", error.SessionSocketRequired);
- spawnTtyWithMount(p, from, true);
+ _ = spawnTtyWithMount(p, from, true);
}
-fn spawnTtyWithMount(p: *Pardes, from: usize, v9fs: bool) void {
- const src = p.panes[from] orelse return;
- const free = p.freeSlot() orelse return;
- const nt = p.newShell(free, Pardes.paneDir(src)) catch return;
+fn spawnTtyWithMount(p: *Pardes, from: usize, v9fs: bool) ?*Pane {
+ const src = p.panes[from] orelse return null;
+ const free = p.freeSlot() orelse {
+ p.reportFailure(from, "Tty: no pane free");
+ return null;
+ };
+ const nt = p.newShell(free, Pardes.paneDir(src)) catch |err| {
+ p.reportError(from, "Tty", err);
+ return null;
+ };
nt.greet = !v9fs;
nt.v9fs_on_spawn = v9fs;
nt.body.mode = .tty;
@@ -317,6 +323,7 @@ fn spawnTtyWithMount(p: *Pardes, from: usize, v9fs: bool) void {
layout.splitBelow(p, parent, nt);
p.active = free;
p.stillborn_joiner = nt.serial;
+ return nt;
}
/// when a doc lands in `col`, a lone pristine shell there is clutter — drop
diff --git a/src/host_io.zig b/src/host_io.zig
index 0f6fd87e..68d35a9c 100644
--- a/src/host_io.zig
+++ b/src/host_io.zig
@@ -743,8 +743,13 @@ pub const Shell = struct {
return .{ .path = path, .argv = .{ path, marks[0], marks[1], null } };
}
- fn find(bin: []const u8, buf: *[std.fs.max_path_bytes]u8) ?[*:0]const u8 {
+ /// A shell by its path or its name in the usual directories, or null.
+ /// A path may be under a mount this editor serves: the turn goes out
+ /// with the lookups (pardes.Turn.yield).
+ pub fn find(bin: []const u8, buf: *[std.fs.max_path_bytes]u8) ?[*:0]const u8 {
if (bin.len == 0 or bin.len + 1 > buf.len) return null;
+ pardes.turn.yield();
+ defer pardes.turn.back();
if (std.mem.indexOfScalar(u8, bin, '/') != null) {
@memcpy(buf[0..bin.len], bin);
buf[bin.len] = 0;
@@ -975,14 +980,22 @@ pub fn forkShell(
if (stat.kind != .directory) return error.NotDir;
break :dir path;
};
- // The turn was given up for the stat: a 9P client may have closed the
- // pane, and another taken its slot, whose command this is not.
- if (core) |c| if ((if (c.panes[pane]) |pn| pn.serial else 0) != serial) return error.PaneGone;
var master: c_int = -1;
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
- // A terminal opened on a shell of its own (`Tty fish`) runs that one.
- const own = if (core) |c| if (c.panes[pane]) |pn| pn.shell else null else null;
+ // A terminal opened on a shell of its own (`Tty fish`) runs that one,
+ // copied: looking it up gives the turn up, and the pane may go.
+ var own_buf: [256]u8 = undefined;
+ var own: ?[]const u8 = null;
+ if (core) |c| if (c.panes[pane]) |pn| if (pn.shell) |name| {
+ const n = @min(name.len, own_buf.len);
+ @memcpy(own_buf[0..n], name[0..n]);
+ own = own_buf[0..n];
+ };
var spawn = Shell.resolve(own orelse bin, &path_buf, prompt_rcs);
+ // The turn was given up for the stat and the lookup: a 9P client may
+ // have closed the pane, and another taken its slot, whose command this
+ // is not.
+ if (core) |c| if ((if (c.panes[pane]) |pn| pn.serial else 0) != serial) return error.PaneGone;
// A command pane's child is the shell running its one line, which has
// no prompt to mark (exec.zig runCommand).
const one_line = if (core) |c| if (c.panes[pane]) |pn| pn.command else null else null;
diff --git a/src/pardes.zig b/src/pardes.zig
index 58354b95..429f33b9 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -1193,7 +1193,7 @@ test "first document fallback splits like Tty from the originating pane" {
const free = p.freeSlot().?;
const doc = try p.openTutorView(free);
exec.placeDoc(p, from, free, doc);
- exec.spawnTty(tty, from);
+ _ = exec.spawnTty(tty, from);
p.sync();
tty.sync();
try std.testing.expectEqual(@as(usize, 2), p.ncol);
@@ -1516,11 +1516,22 @@ test "Tty+fish, one word a tag can hold, opens a terminal on that shell" {
const p = try Pardes.init(std.testing.allocator, .{ .tty_only = true });
defer p.deinit();
while (p.nextEffect()) |_| {}
- try std.testing.expect(p.executeBuiltinLine(0, "Tty+fish"));
+ // sh, the one shell every host has.
+ try std.testing.expect(p.executeBuiltinLine(0, "Tty+sh"));
try std.testing.expect(p.active != 0);
- try std.testing.expectEqualStrings("fish", p.panes[p.active].?.shell.?);
- p.acknowledgeShell(p.active, "/usr/bin/fish", true);
- try std.testing.expect(std.mem.startsWith(u8, tagline.curTail(p.panes[p.active].?), " Tty+fish "));
+ try std.testing.expectEqualStrings("sh", p.panes[p.active].?.shell.?);
+ p.acknowledgeShell(p.active, "/bin/sh", false);
+ try std.testing.expect(std.mem.startsWith(u8, tagline.curTail(p.panes[p.active].?), " Tty+sh "));
+ p.active = 0;
+ // A shell that is not there is said, not quietly replaced, and the
+ // pane that asked gets no shell of its own.
+ const before = p.active;
+ const caller = p.panes[0].?;
+ try std.testing.expect(p.executeBuiltinLine(0, "Tty /nonexistent"));
+ try std.testing.expect(p.active == before and caller.shell == null);
+ try std.testing.expect(std.mem.indexOf(u8, caller.msg[0..caller.msg_len], "no shell \"/nonexistent\"") != null);
+ try std.testing.expect(p.executeBuiltinLine(0, "Tty fsh-not-a-shell"));
+ try std.testing.expect(caller.shell == null);
// Only a word that says so splits at `+`: `Dump+x.zon` is no Dump.
try std.testing.expect(!p.executeBuiltinLine(0, "Dump+x.zon"));
try std.testing.expect(!p.executeBuiltinLine(0, "Msg+hello"));
@@ -7059,7 +7070,7 @@ test "raw tty Escape only leaves at a shell prompt" {
if (platform == .web) return;
const p = try Pardes.init(std.testing.allocator, .{ .cols = 80, .rows = 24, .file = "mise.toml" });
defer p.deinit();
- exec.spawnTty(p, 0);
+ _ = exec.spawnTty(p, 0);
const shell = p.active;
const pane = p.panes[shell].?;
var buf: [256]u8 = undefined;
@@ -7090,7 +7101,7 @@ test "raw tty Ctrl-B switches to editor mode without sending child input" {
if (platform == .web) return;
const p = try Pardes.init(std.testing.allocator, .{ .file = "mise.toml" });
defer p.deinit();
- exec.spawnTty(p, 0);
+ _ = exec.spawnTty(p, 0);
const shell = p.active;
p.sync();
var buf: [256]u8 = undefined;