summaryrefslogtreecommitdiff
path: root/src
diff options
context:
space:
mode:
Diffstat (limited to 'src')
-rw-r--r--src/body_layer.zig2
-rw-r--r--src/builtins.zig7
-rw-r--r--src/exec.zig3
-rw-r--r--src/host_io.zig4
-rw-r--r--src/panes.zig7
-rw-r--r--src/pardes.zig25
-rw-r--r--src/tagline.zig2
7 files changed, 45 insertions, 5 deletions
diff --git a/src/body_layer.zig b/src/body_layer.zig
index f9e26b10..c468bd5f 100644
--- a/src/body_layer.zig
+++ b/src/body_layer.zig
@@ -676,7 +676,7 @@ fn tagTintRange(pane: *const Pane, tag: []const u8, prefix_len: usize) ?struct {
if (pane.isTerminal()) {
var words = std.mem.tokenizeAny(u8, tag[prefix_len..], " \t\r\n");
while (words.next()) |word| {
- if (!std.mem.eql(u8, word, "Tty")) continue;
+ if (!std.mem.eql(u8, word, "Tty") and !std.mem.startsWith(u8, word, "Tty+")) continue;
const lo = @intFromPtr(word.ptr) - @intFromPtr(tag.ptr);
return .{ .lo = lo, .hi = lo + word.len };
}
diff --git a/src/builtins.zig b/src/builtins.zig
index 19d16e1e..02787cf5 100644
--- a/src/builtins.zig
+++ b/src/builtins.zig
@@ -818,9 +818,16 @@ pub const DelBelow = struct {
/// A shell in the calling pane's directory, raw from the first frame. On every
/// pane's tagline: the fast path from wherever you are to a prompt there.
+/// A terminal below the caller in its directory: the configured shell, or
+/// the one named, `Tty fish`, as a terminal's own tag word `Tty+fish` says.
pub const Tty = struct {
+ pub const takes_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;
}
};
diff --git a/src/exec.zig b/src/exec.zig
index 07d5e92f..e2b9f880 100644
--- a/src/exec.zig
+++ b/src/exec.zig
@@ -488,7 +488,8 @@ pub fn commandText(txt: []const u8) []const u8 {
pub fn executeBuiltinLine(p: *Pardes, id: usize, txt: []const u8) bool {
const cmd = commandText(txt);
if (cmd.len == 0 or p.exec_depth >= max_exec_depth) return false;
- const sp = std.mem.indexOfAny(u8, cmd, " \t");
+ // `Word arg`, or as one word a tag can hold, `Word+arg` (`Tty+fish`).
+ const sp = std.mem.indexOfAny(u8, cmd, " \t") orelse std.mem.indexOfScalar(u8, cmd, '+');
const bi: ?Builtin = std.meta.stringToEnum(Builtin, cmd) orelse blk: {
const head = std.meta.stringToEnum(Builtin, cmd[0 .. sp orelse break :blk null]) orelse break :blk null;
break :blk if (builtins.registry.takesArg(head)) head else null;
diff --git a/src/host_io.zig b/src/host_io.zig
index 820cb2b6..63b907ac 100644
--- a/src/host_io.zig
+++ b/src/host_io.zig
@@ -976,7 +976,9 @@ pub fn forkShell(
};
var master: c_int = -1;
var path_buf: [std.fs.max_path_bytes]u8 = undefined;
- var spawn = Shell.resolve(bin, &path_buf, prompt_rcs);
+ // 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;
+ var spawn = Shell.resolve(own orelse bin, &path_buf, prompt_rcs);
// 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/panes.zig b/src/panes.zig
index a7f7f69f..2f8b6b56 100644
--- a/src/panes.zig
+++ b/src/panes.zig
@@ -109,6 +109,13 @@ pub const Pane = struct {
/// shown `exit ?`).
command_done: bool = false,
command_status: ?u8 = null,
+ /// The shell a terminal runs when it is not the configured one (`Tty
+ /// fish`), owned.
+ shell: ?[]u8 = null,
+ /// An interactive terminal's tag text naming its shell, ` Tty+fish ...`
+ /// (Pardes.acknowledgeShell); empty until the host says which it ran.
+ shell_tail: [96]u8 = undefined,
+ shell_tail_len: u8 = 0,
/// The revision an Exit or a Restore last warned this pane was modified
/// at, and which of them: only that word goes ahead on the warning
/// (builtins.zig warnModified).
diff --git a/src/pardes.zig b/src/pardes.zig
index 1bb13e3d..e92fb3f7 100644
--- a/src/pardes.zig
+++ b/src/pardes.zig
@@ -631,8 +631,9 @@ test "pane-tag Exec prefers Tty and argument builtins before shell fallback" {
while (p.nextEffect()) |_| {} // initial shell spawn/geometry
p.acknowledgeShell(0, "/bin/sh", false);
+ // The Tty word names the shell the host ran, and opens another of it.
const pane = p.panes[0].?;
- try std.testing.expectEqualStrings(tagline.terminal_pane_tail, tagline.curTail(pane));
+ try std.testing.expectEqualStrings(" Tty+sh" ++ tagline.terminal_pane_tail[" Tty".len..], tagline.curTail(pane));
const tag_x = p.rects[0].x + TAG_TEXT_INSET;
const tag_y = p.rects[0].y;
@@ -654,6 +655,7 @@ test "pane-tag Exec prefers Tty and argument builtins before shell fallback" {
};
try std.testing.expectEqual(panes_before + 1, panes_after);
try std.testing.expect(p.panes[p.active].?.isTerminal());
+ try std.testing.expectEqualStrings("sh", p.panes[p.active].?.shell.?);
// Argument-taking builtins use the same gesture path: a tag sweep is the
// complete command line, and Theme consumes its tail without a PTY write.
@@ -1359,6 +1361,17 @@ test "Exec in a terminal whose tty is taken runs as a command pane instead of ty
try std.testing.expect(spawned);
}
+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"));
+ 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 "));
+}
+
test "a command pane shows how its command ended, and the next command there runs below it" {
const gpa = std.testing.allocator;
const p = try Pardes.init(gpa, .{});
@@ -4188,6 +4201,7 @@ pub const Pardes = struct {
for (0..pane.pointer_selections.len) |slot| pane.clearPointerSelection(slot);
panes.Terminal.deinitPendingCommand(pane);
if (pane.command) |line| p.gpa.free(line);
+ if (pane.shell) |bin| p.gpa.free(bin);
if (pane.image) |*iv| {
iv.deinit(p.image_gpa);
}
@@ -4371,6 +4385,15 @@ pub const Pardes = struct {
if (id < MAX_PANES) if (p.panes[id]) |pane| {
ctlfs.pty.shellGone(p, pane, true); // a respawn replaced whatever ran
pane.fs.unmarked = !prompt_marks;
+ // The tag's Tty word names the shell the host ran, and a click
+ // on it (`Tty+fish`) opens another of the same.
+ if (pane.command == null) {
+ const base = std.fs.path.basename(executable);
+ const rest = tagline.terminal_pane_tail[" Tty".len..];
+ if (std.fmt.bufPrint(&pane.shell_tail, " Tty+{s}{s}", .{ base, rest })) |tail| {
+ pane.shell_tail_len = @intCast(tail.len);
+ } else |_| {}
+ }
};
if (id < MAX_PANES) panes.Terminal.shellSpawned(p, id, prompt_marks);
if (p.settings.shell.effective.set(executable))
diff --git a/src/tagline.zig b/src/tagline.zig
index 8b214fa6..8c1efa7b 100644
--- a/src/tagline.zig
+++ b/src/tagline.zig
@@ -158,7 +158,7 @@ pub fn defaultTail(pane: *const Pane) []const u8 {
if (panes.File.supportsContext(pane)) return context_pane_tail;
if (pane.file != null) return file_pane_tail;
if (pane.command != null) return command_pane_tail;
- if (pane.isTerminal()) return terminal_pane_tail;
+ if (pane.isTerminal()) return if (pane.shell_tail_len > 0) pane.shell_tail[0..pane.shell_tail_len] else terminal_pane_tail;
return pane_tail;
}