Build in the review tool.

This commit is contained in:
Greyson Parrelli committed 2026-08-24 09:24:01 -04:00
1 parent 4eef6a28d3
commit 407dd81512
55 files changed
+12932 -40

No files matched your search

+33 -4
View File
@@ -92,11 +92,16 @@ pub const Error = error{
/// `dir` is the directory the child starts in. A null, or a directory that
/// cannot be entered, leaves it wherever the app was started — a layout
/// naming a path that no longer exists should still give you a usable shell.
///
/// `env_extra` is added to the child's environment as `NAME=value` strings,
/// replacing any the parent already had under the same name. This is how a
/// shell learns about the tab it is running in.
pub fn create(
alloc: std.mem.Allocator,
path: [:0]const u8,
argv: []const [:0]const u8,
dir: ?[:0]const u8,
env_extra: []const []const u8,
size: Winsize,
) !Pty {
const master = c.posix_openpt(O_RDWR | O_NOCTTY);
@@ -118,7 +123,7 @@ pub fn create(
defer alloc.free(argv_z);
for (argv, 0..) |arg, i| argv_z[i] = arg.ptr;
const envp_z = try buildEnv(alloc);
const envp_z = try buildEnv(alloc, env_extra);
defer freeEnv(alloc, envp_z);
const slave_path_z = try alloc.dupeZ(u8, slave_path);
@@ -198,9 +203,18 @@ pub fn loginShell(buf: []u8) ?[]const u8 {
}
/// Copy the current environment, forcing the variables that describe what
/// kind of terminal we are. We advertise xterm-256color rather than
/// ghostty's own terminfo because we don't install a terminfo entry.
fn buildEnv(alloc: std.mem.Allocator) ![:null]?[*:0]const u8 {
/// kind of terminal we are, and adding whatever the caller supplied. We
/// advertise xterm-256color rather than ghostty's own terminfo because we
/// don't install a terminfo entry.
///
/// Anything we are about to define is dropped from the inherited copy first,
/// so a variable set in playpen's own environment cannot shadow the value this
/// pane is supposed to see — which matters most for the ones that describe the
/// pane itself, since inheriting a stale one is worse than having none.
fn buildEnv(
alloc: std.mem.Allocator,
extra: []const []const u8,
) ![:null]?[*:0]const u8 {
var list: std.ArrayListUnmanaged([*:0]const u8) = .empty;
defer list.deinit(alloc);
errdefer for (list.items) |item| alloc.free(std.mem.span(item));
@@ -211,16 +225,31 @@ fn buildEnv(alloc: std.mem.Allocator) ![:null]?[*:0]const u8 {
// Drop the variables we're about to define ourselves.
if (std.mem.startsWith(u8, span, "TERM=")) continue;
if (std.mem.startsWith(u8, span, "COLORTERM=")) continue;
if (shadowedBy(span, extra)) continue;
try list.append(alloc, (try alloc.dupeZ(u8, span)).ptr);
}
try list.append(alloc, (try alloc.dupeZ(u8, "TERM=xterm-256color")).ptr);
try list.append(alloc, (try alloc.dupeZ(u8, "COLORTERM=truecolor")).ptr);
for (extra) |entry| {
try list.append(alloc, (try alloc.dupeZ(u8, entry)).ptr);
}
const result = try alloc.allocSentinel(?[*:0]const u8, list.items.len, null);
for (list.items, 0..) |item, idx| result[idx] = item;
return result;
}
/// Whether an inherited `NAME=value` entry names a variable the caller is
/// about to define.
fn shadowedBy(entry: []const u8, extra: []const []const u8) bool {
const eq = std.mem.indexOfScalar(u8, entry, '=') orelse return false;
for (extra) |candidate| {
const candidate_eq = std.mem.indexOfScalar(u8, candidate, '=') orelse continue;
if (std.mem.eql(u8, entry[0 .. eq + 1], candidate[0 .. candidate_eq + 1])) return true;
}
return false;
}
fn freeEnv(alloc: std.mem.Allocator, envp: [:null]?[*:0]const u8) void {
for (envp) |entry| if (entry) |e| alloc.free(std.mem.span(e));
alloc.free(envp);