diff options
Diffstat (limited to 'src')
| -rw-r--r-- | src/body_layer.zig | 2 | ||||
| -rw-r--r-- | src/builtins.zig | 7 | ||||
| -rw-r--r-- | src/exec.zig | 3 | ||||
| -rw-r--r-- | src/host_io.zig | 4 | ||||
| -rw-r--r-- | src/panes.zig | 7 | ||||
| -rw-r--r-- | src/pardes.zig | 25 | ||||
| -rw-r--r-- | src/tagline.zig | 2 |
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; } |
