Update to Zig 0.16.0

This commit represents the majority of the work necessary to upgrade
Ghostty to use Zig 0.16.0.

Key parts:

* In addition to its previous responsibilities, the global state now
  houses state for global I/O implementations and the process
  environment. It is now also utilized in the main application along
  with the C library. Where necessary, global state is isolated from key
  parts of the implementation (e.g., in libghostty subsystems), and it's
  expected that this list will grow.

* We currently manage our own C translation layer where necessary. In
  these cases, cImport has been removed in favor of the new external
  translate-c package. Due to fixes that have needed be made to properly
  translate the dependencies that were swapped out, as mentioned, we
  have had to backport fixes from the current translate-c package (and
  the upstream Arocc dependency). We will host this ourselves until Zig
  0.17.0 is released with these fixes.

* Where necessary (only a small number of cases), some stdlib code from
  0.15.2 (and even from 0.17.0) has been taken, adopted, and vendored in
  lib/compat.

Co-authored-by: Leah Amelia Chen <hi@pluie.me>
This commit is contained in:
Chris Marchesi
2026-05-07 09:11:14 -07:00
parent 74d0c72fd9
commit e8525c0fd9
357 changed files with 8958 additions and 6098 deletions

View File

@@ -6,29 +6,31 @@
//! Setting either env var to an empty string disables paging.
//! If stdout is not a TTY, writes go directly to stdout.
const Pager = @This();
const builtin = @import("builtin");
const std = @import("std");
const Allocator = std.mem.Allocator;
const internal_os = @import("../os/main.zig");
const global = @import("../global.zig");
/// The pager child process, if one was spawned.
child: ?std.process.Child = null,
/// The buffered file writer used for both the pager pipe and direct
/// stdout paths.
file_writer: std.fs.File.Writer = undefined,
file_writer: std.Io.File.Writer = undefined,
/// Initialize the pager. If stdout is a TTY, this spawns the pager
/// process. Otherwise, output goes directly to stdout.
pub fn init(alloc: Allocator) Pager {
return .{ .child = initPager(alloc) };
pub fn init() Pager {
return .{ .child = initPager() };
}
/// Writes to the pager process if available; otherwise, stdout.
pub fn writer(self: *Pager, buffer: []u8) *std.Io.Writer {
if (self.child) |child| {
self.file_writer = child.stdin.?.writer(buffer);
self.file_writer = child.stdin.?.writer(global.io(), buffer);
} else {
self.file_writer = std.fs.File.stdout().writer(buffer);
self.file_writer = std.Io.File.stdout().writer(global.io(), buffer);
}
return &self.file_writer.interface;
}
@@ -40,45 +42,44 @@ pub fn deinit(self: *Pager) void {
// pager sees EOF, then wait for it to exit.
self.file_writer.interface.flush() catch {};
if (child.stdin) |stdin| {
stdin.close();
stdin.close(global.io());
child.stdin = null;
}
_ = child.wait() catch {};
_ = child.wait(global.io()) catch {};
}
self.* = undefined;
}
fn initPager(alloc: Allocator) ?std.process.Child {
const stdout_file: std.fs.File = .stdout();
if (!stdout_file.isTty()) return null;
fn initPager() ?std.process.Child {
const stdout_file: std.Io.File = .stdout();
if (!(stdout_file.isTty(global.io()) catch return null)) return null;
var env = global.environMap() catch return null;
defer env.deinit();
// Resolve the pager command: $GHOSTTY_PAGER > $PAGER > `less`.
// An empty value for either env var disables paging.
const ghostty_var = internal_os.getenv(alloc, "GHOSTTY_PAGER") catch null;
defer if (ghostty_var) |v| v.deinit(alloc);
const pager_var = internal_os.getenv(alloc, "PAGER") catch null;
defer if (pager_var) |v| v.deinit(alloc);
const ghostty_var = env.get("GHOSTTY_PAGER");
const pager_var = env.get("PAGER");
const cmd: ?[]const u8 = cmd: {
if (ghostty_var) |v| break :cmd if (v.value.len > 0) v.value else null;
if (pager_var) |v| break :cmd if (v.value.len > 0) v.value else null;
if (ghostty_var) |v| break :cmd if (v.len > 0) v else null;
if (pager_var) |v| break :cmd if (v.len > 0) v else null;
break :cmd "less";
};
if (cmd == null) return null;
var child: std.process.Child = .init(&.{cmd.?}, alloc);
child.stdin_behavior = .Pipe;
child.stdout_behavior = .Inherit;
child.stderr_behavior = .Inherit;
child.spawn() catch return null;
return child;
return std.process.spawn(global.io(), .{
.argv = &.{cmd.?},
.stdin = .pipe,
.stdout = .inherit,
.stderr = .inherit,
}) catch null;
}
test "pager: non-tty" {
var pager: Pager = .init(std.testing.allocator);
var pager: Pager = .init();
defer pager.deinit();
try std.testing.expect(pager.child == null);
}

View File

@@ -11,8 +11,8 @@ pub const DetectError = error{
};
/// Detect the action from CLI args.
pub fn detectArgs(comptime E: type, alloc: Allocator) !?E {
var iter = try std.process.argsWithAllocator(alloc);
pub fn detectArgs(comptime E: type, alloc: Allocator, args: std.process.Args) !?E {
var iter = try args.iterateAllocator(alloc);
defer iter.deinit();
return try detectIter(E, &iter);
}
@@ -83,7 +83,7 @@ test "detect direct match" {
const alloc = testing.allocator;
const Enum = enum { foo, bar, baz };
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"+foo",
);
@@ -97,7 +97,7 @@ test "detect invalid match" {
const alloc = testing.allocator;
const Enum = enum { foo, bar, baz };
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"+invalid",
);
@@ -113,7 +113,7 @@ test "detect multiple actions" {
const alloc = testing.allocator;
const Enum = enum { foo, bar, baz };
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"+foo +bar",
);
@@ -129,7 +129,7 @@ test "detect no match" {
const alloc = testing.allocator;
const Enum = enum { foo, bar, baz };
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"--some-flag",
);
@@ -154,7 +154,7 @@ test "detect special case action" {
};
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"--special +bar",
);
@@ -164,7 +164,7 @@ test "detect special case action" {
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"+bar --special",
);
@@ -174,7 +174,7 @@ test "detect special case action" {
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"+bar",
);
@@ -200,7 +200,7 @@ test "detect special case fallback" {
};
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"--special",
);
@@ -210,7 +210,7 @@ test "detect special case fallback" {
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"+bar --special",
);
@@ -220,7 +220,7 @@ test "detect special case fallback" {
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"--special +bar",
);
@@ -246,7 +246,7 @@ test "detect special case abort_if_no_action" {
};
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"-e",
);
@@ -256,7 +256,7 @@ test "detect special case abort_if_no_action" {
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"+foo -e",
);
@@ -266,7 +266,7 @@ test "detect special case abort_if_no_action" {
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"-e +bar",
);

View File

@@ -489,18 +489,13 @@ pub fn parseTaggedUnion(comptime T: type, alloc: Allocator, v: []const u8) !T {
// We need to create a struct that looks like this union field.
// This lets us use parseIntoField as if its a dedicated struct.
const Target = @Type(.{ .@"struct" = .{
.layout = .auto,
.fields = &.{.{
.name = field.name,
.type = field.type,
.default_value_ptr = null,
.is_comptime = false,
.alignment = @alignOf(field.type),
}},
.decls = &.{},
.is_tuple = false,
} });
const Target = @Struct(
.auto,
null,
&.{field.name},
&.{field.type},
&.{.{ .@"align" = @alignOf(field.type) }},
);
// Parse the value into the struct
var t: Target = undefined;
@@ -677,7 +672,7 @@ test "parse: simple" {
} = .{};
defer if (data._arena) |arena| arena.deinit();
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
testing.allocator,
"--a=42 --b --b-f=false",
);
@@ -689,7 +684,7 @@ test "parse: simple" {
try testing.expect(!data.@"b-f");
// Reparsing works
var iter2 = try std.process.ArgIteratorGeneral(.{}).init(
var iter2 = try std.process.Args.IteratorGeneral(.{}).init(
testing.allocator,
"--a=84",
);
@@ -711,7 +706,7 @@ test "parse: quoted value" {
} = .{};
defer if (data._arena) |arena| arena.deinit();
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
testing.allocator,
"--a=\"42\" --b=\"hello!\"",
);
@@ -731,7 +726,7 @@ test "parse: empty value resets to default" {
} = .{};
defer if (data._arena) |arena| arena.deinit();
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
testing.allocator,
"--a= --b=",
);
@@ -750,7 +745,7 @@ test "parse: positional arguments are invalid" {
} = .{};
defer if (data._arena) |arena| arena.deinit();
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
testing.allocator,
"--a=84 what",
);
@@ -774,7 +769,7 @@ test "parse: diagnostic tracking" {
} = .{};
defer if (data._arena) |arena| arena.deinit();
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
testing.allocator,
"--what --a=42",
);
@@ -858,7 +853,7 @@ test "parse: compatibility handler" {
} = .{};
defer if (data._arena) |arena| arena.deinit();
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
testing.allocator,
"--a=yuh",
);
@@ -884,7 +879,7 @@ test "parse: compatibility renamed" {
} = .{};
defer if (data._arena) |arena| arena.deinit();
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
testing.allocator,
"--old=true --b=true",
);
@@ -1363,8 +1358,11 @@ pub fn ArgsIterator(comptime Iterator: type) type {
}
/// Create an args iterator for the process args. This will skip argv0.
pub fn argsIterator(alloc_gpa: Allocator) internal_os.args.ArgIterator.InitError!ArgsIterator(internal_os.args.ArgIterator) {
var iter = try internal_os.args.iterator(alloc_gpa);
pub fn argsIterator(
alloc_gpa: Allocator,
args: std.process.Args,
) std.process.Args.Iterator.InitError!ArgsIterator(std.process.Args.Iterator) {
var iter: std.process.Args.Iterator = try .initAllocator(args, alloc_gpa);
errdefer iter.deinit();
_ = iter.next(); // skip argv0
return .{ .iterator = iter };
@@ -1373,7 +1371,7 @@ pub fn argsIterator(alloc_gpa: Allocator) internal_os.args.ArgIterator.InitError
test "ArgsIterator" {
const testing = std.testing;
const child = try std.process.ArgIteratorGeneral(.{}).init(
const child = try std.process.Args.IteratorGeneral(.{}).init(
testing.allocator,
"--what +list-things --a=42",
);

View File

@@ -3,6 +3,7 @@ const builtin = @import("builtin");
const args = @import("args.zig");
const Action = @import("ghostty.zig").Action;
const Allocator = std.mem.Allocator;
const global = @import("../global.zig");
const vaxis = @import("vaxis");
const framedata = @import("framedata").compressed;
@@ -173,6 +174,9 @@ const Boo = struct {
/// The `boo` command is used to display the animation from the Ghostty website in the terminal
pub fn run(gpa: Allocator) !u8 {
var env_map = try global.environMap();
defer env_map.deinit();
// Disable on non-desktop systems.
switch (builtin.os.tag) {
.windows, .macos, .linux, .freebsd => {},
@@ -183,7 +187,7 @@ pub fn run(gpa: Allocator) !u8 {
defer opts.deinit();
{
var iter = try args.argsIterator(gpa);
var iter = try args.argsIterator(gpa, global.args());
defer iter.deinit();
try args.parse(Options, gpa, &opts, &iter);
}
@@ -194,7 +198,7 @@ pub fn run(gpa: Allocator) !u8 {
gpa.free(decompressed_data);
}
var app = try vxfw.App.init(gpa);
var app = try vxfw.App.init(global.io(), gpa, &env_map, &.{});
defer app.deinit();
var boo: Boo = undefined;

View File

@@ -4,6 +4,7 @@ const args = @import("args.zig");
const Action = @import("ghostty.zig").Action;
const Config = @import("../config.zig").Config;
const crash = @import("../crash/main.zig");
const global = @import("../global.zig");
pub const Options = struct {
pub fn deinit(self: Options) void {
@@ -33,14 +34,14 @@ pub fn run(alloc_gpa: Allocator) !u8 {
defer opts.deinit();
{
var iter = try args.argsIterator(alloc_gpa);
var iter = try args.argsIterator(alloc_gpa, global.args());
defer iter.deinit();
try args.parse(Options, alloc_gpa, &opts, &iter);
}
var buffer: [1024]u8 = undefined;
var stdout_file: std.fs.File = .stdout();
var stdout_writer = stdout_file.writer(&buffer);
var stdout_file: std.Io.File = .stdout();
var stdout_writer = stdout_file.writer(global.io(), &buffer);
const stdout = &stdout_writer.interface;
const result = runInner(alloc, &stdout_file, stdout);
@@ -50,7 +51,7 @@ pub fn run(alloc_gpa: Allocator) !u8 {
fn runInner(
alloc: Allocator,
stdout_file: *std.fs.File,
stdout_file: *std.Io.File,
stdout: *std.Io.Writer,
) !u8 {
const crash_dir = try crash.defaultDir(alloc);
@@ -66,7 +67,7 @@ fn runInner(
// If we have no reports, then we're done. If we have a tty then we
// print a message, otherwise we do nothing.
if (reports.items.len == 0) {
if (std.posix.isatty(stdout_file.handle)) {
if (try stdout_file.isTty(global.io())) {
try stdout.writeAll("No crash reports! 👻\n");
}
return 0;
@@ -76,7 +77,7 @@ fn runInner(
for (reports.items) |report| {
var buf: [128]u8 = undefined;
const now = std.time.nanoTimestamp();
const now = std.Io.Timestamp.now(global.io(), .real).toNanoseconds();
const diff = now - report.mtime;
const since = if (diff <= 0) "now" else s: {
const d = Config.Duration{ .duration = @intCast(diff) };

View File

@@ -93,7 +93,7 @@ pub const Location = union(enum) {
/// and potentially in the future structure them differently.
pub const DiagnosticList = struct {
/// The list of diagnostics.
list: std.ArrayListUnmanaged(Diagnostic) = .{},
list: std.ArrayList(Diagnostic) = .empty,
/// Precomputed data for diagnostics. This is used specifically
/// when we build libghostty so that we can precompute the messages
@@ -111,7 +111,7 @@ pub const DiagnosticList = struct {
};
const Precompute = if (precompute_enabled) struct {
messages: std.ArrayListUnmanaged([:0]const u8) = .{},
messages: std.ArrayList([:0]const u8) = .empty,
pub fn clone(
self: *const Precompute,

View File

@@ -7,6 +7,7 @@ const Action = @import("ghostty.zig").Action;
const configpkg = @import("../config.zig");
const internal_os = @import("../os/main.zig");
const Config = configpkg.Config;
const global = @import("../global.zig");
pub const Options = struct {
pub fn deinit(self: Options) void {
@@ -48,14 +49,14 @@ pub fn run(alloc: Allocator) !u8 {
// critical where setting up the defer cleanup is a problem.
var buffer: [1024]u8 = undefined;
var stderr_writer = std.fs.File.stderr().writer(&buffer);
var stderr_writer = std.Io.File.stderr().writer(global.io(), &buffer);
const stderr = &stderr_writer.interface;
var opts: Options = .{};
defer opts.deinit();
{
var iter = try args.argsIterator(alloc);
var iter = try args.argsIterator(alloc, global.args());
defer iter.deinit();
try args.parse(Options, alloc, &opts, &iter);
}
@@ -67,6 +68,12 @@ pub fn run(alloc: Allocator) !u8 {
}
fn runInner(alloc: Allocator, stderr: *std.Io.Writer) !u8 {
// We require libc because we want to use std.c.environ for envp
// and not have to build that ourselves. We can remove this
// limitation later but Ghostty already heavily requires libc
// so this is not a big deal.
comptime assert(builtin.link_libc);
// We load the configuration once because that will write our
// default configuration files to disk. We don't use the config.
var config = try Config.load(alloc);
@@ -91,22 +98,19 @@ fn runInner(alloc: Allocator, stderr: *std.Io.Writer) !u8 {
}
// Get our editor
const get_env_: ?internal_os.GetEnvResult = env: {
const editor = env: {
// VISUAL vs. EDITOR: https://unix.stackexchange.com/questions/4859/visual-vs-editor-what-s-the-difference
if (try internal_os.getenv(alloc, "VISUAL")) |v| {
if (v.value.len > 0) break :env v;
v.deinit(alloc);
if (try global.environ().containsUnempty(alloc, "VISUAL")) {
break :env try global.environ().getAlloc(alloc, "VISUAL");
}
if (try internal_os.getenv(alloc, "EDITOR")) |v| {
if (v.value.len > 0) break :env v;
v.deinit(alloc);
if (try global.environ().containsUnempty(alloc, "EDITOR")) {
break :env try global.environ().getAlloc(alloc, "EDITOR");
}
break :env null;
break :env "";
};
defer if (get_env_) |v| v.deinit(alloc);
const editor: []const u8 = if (get_env_) |v| v.value else "";
defer alloc.free(editor);
// If we don't have `$EDITOR` set then we can't do anything
// but we can still print a helpful message.
@@ -136,8 +140,9 @@ fn runInner(alloc: Allocator, stderr: *std.Io.Writer) !u8 {
return 1;
}
// Build the command
const command = command: {
var buffer: std.io.Writer.Allocating = .init(alloc);
var buffer: std.Io.Writer.Allocating = .init(alloc);
defer buffer.deinit();
const writer = &buffer.writer;
try writer.writeAll(editor);
@@ -152,21 +157,14 @@ fn runInner(alloc: Allocator, stderr: *std.Io.Writer) !u8 {
};
defer alloc.free(command);
// We require libc because we want to use std.c.environ for envp
// and not have to build that ourselves. We can remove this
// limitation later but Ghostty already heavily requires libc
// so this is not a big deal.
comptime assert(builtin.link_libc);
const err = std.posix.execvpeZ(
"/bin/sh",
&.{ "/bin/sh", "-c", command },
std.c.environ,
);
// Run/replace process (using execve)
const argv = &.{ "/bin/sh", "-c", command };
const envp = std.c.environ;
const err = std.posix.errno(std.posix.system.execve(argv[0], @ptrCast(argv), envp));
// If we reached this point then exec failed.
try stderr.print(
\\Failed to execute the editor. Error code={}.
\\Failed to execute the editor (E{s}).
\\
\\This is usually due to the executable path not existing, invalid
\\permissions, or the shell environment not being set up
@@ -175,6 +173,6 @@ fn runInner(alloc: Allocator, stderr: *std.Io.Writer) !u8 {
\\Editor: {s}
\\Path: {s}
\\
, .{ err, editor, path });
, .{ @tagName(err), editor, path });
return 1;
}

View File

@@ -7,6 +7,7 @@ const Config = @import("../config/Config.zig");
const ConfigKey = @import("../config/key.zig").Key;
const KeybindAction = @import("../input/Binding.zig").Action;
const Pager = @import("Pager.zig");
const global = @import("../global.zig");
pub const Options = struct {
/// The config option to explain. For example:
@@ -51,7 +52,7 @@ pub fn run(alloc: Allocator) !u8 {
var positional: ?[]const u8 = null;
var no_pager: bool = false;
var iter = try args.argsIterator(alloc);
var iter = try args.argsIterator(alloc, global.args());
defer iter.deinit();
defer if (option_name) |s| alloc.free(s);
defer if (keybind_name) |s| alloc.free(s);
@@ -75,9 +76,9 @@ pub fn run(alloc: Allocator) !u8 {
// respective lookup. A bare positional argument tries config
// options first, then keybind actions as a fallback.
const name = keybind_name orelse option_name orelse positional orelse {
var stderr: std.fs.File = .stderr();
var stderr: std.Io.File = .stderr();
var buffer: [4096]u8 = undefined;
var stderr_writer = stderr.writer(&buffer);
var stderr_writer = stderr.writer(global.io(), &buffer);
try stderr_writer.interface.writeAll("Usage: ghostty +explain-config <option>\n");
try stderr_writer.interface.writeAll(" ghostty +explain-config --option=<option>\n");
try stderr_writer.interface.writeAll(" ghostty +explain-config --keybind=<action>\n");
@@ -92,7 +93,7 @@ pub fn run(alloc: Allocator) !u8 {
else
explainOption(name) orelse explainKeybind(name);
var pager: Pager = if (!no_pager) .init(alloc) else .{};
var pager: Pager = if (!no_pager) .init() else .{};
defer pager.deinit();
var buffer: [4096]u8 = undefined;
const writer = pager.writer(&buffer);

View File

@@ -22,6 +22,7 @@ const show_face = @import("show_face.zig");
const boo = @import("boo.zig");
const new_window = @import("new_window.zig");
const toggle_quick_terminal = @import("toggle_quick_terminal.zig");
const global = @import("../global.zig");
/// Special commands that can be invoked via CLI flags. These are all
/// invoked by using `+<action>` as a CLI flag. The only exception is
@@ -120,7 +121,10 @@ pub const Action = enum {
if (std.mem.eql(u8, field.name, @tagName(self))) {
var buffer: [1024]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
var stdout_writer = std.Io.File.stdout().writer(
global.io(),
&buffer,
);
const stdout = &stdout_writer.interface;
const text = @field(help_strings.Action, field.name) ++ "\n";
stdout.writeAll(text) catch |write_err| {
@@ -213,7 +217,7 @@ test "parse action none" {
const testing = std.testing;
const alloc = testing.allocator;
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"--a=42 --b --b-f=false",
);
@@ -227,7 +231,7 @@ test "parse action version" {
const alloc = testing.allocator;
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"--a=42 --b --b-f=false --version",
);
@@ -237,7 +241,7 @@ test "parse action version" {
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"--version --a=42 --b --b-f=false",
);
@@ -247,7 +251,7 @@ test "parse action version" {
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"--c=84 --d --version --a=42 --b --b-f=false",
);
@@ -262,7 +266,7 @@ test "parse action plus" {
const alloc = testing.allocator;
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"--a=42 --b --b-f=false +version",
);
@@ -272,7 +276,7 @@ test "parse action plus" {
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"+version --a=42 --b --b-f=false",
);
@@ -282,7 +286,7 @@ test "parse action plus" {
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"--c=84 --d +version --a=42 --b --b-f=false",
);
@@ -297,7 +301,7 @@ test "parse action plus ignores -e" {
const alloc = testing.allocator;
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"--a=42 -e +version",
);
@@ -307,7 +311,7 @@ test "parse action plus ignores -e" {
}
{
var iter = try std.process.ArgIteratorGeneral(.{}).init(
var iter = try std.process.Args.IteratorGeneral(.{}).init(
alloc,
"+list-fonts --a=42 -e +version",
);

View File

@@ -2,6 +2,7 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const args = @import("args.zig");
const Action = @import("ghostty.zig").Action;
const global = @import("../global.zig");
// Note that this options struct doesn't implement the `help` decl like other
// actions. That is because the help command is special and wants to handle its
@@ -25,13 +26,13 @@ pub fn run(alloc: Allocator) !u8 {
defer opts.deinit();
{
var iter = try args.argsIterator(alloc);
var iter = try args.argsIterator(alloc, global.args());
defer iter.deinit();
try args.parse(Options, alloc, &opts, &iter);
}
var buffer: [2048]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
var stdout_writer = std.Io.File.stdout().writer(global.io(), &buffer);
const stdout = &stdout_writer.interface;
try stdout.writeAll(
\\Usage: ghostty [+action] [options]

View File

@@ -3,6 +3,7 @@ const args = @import("args.zig");
const Action = @import("ghostty.zig").Action;
const Allocator = std.mem.Allocator;
const helpgen_actions = @import("../input/helpgen_actions.zig");
const global = @import("../global.zig");
pub const Options = struct {
/// If `true`, print out documentation about the action associated with the
@@ -32,14 +33,14 @@ pub fn run(alloc: Allocator) !u8 {
defer opts.deinit();
{
var iter = try args.argsIterator(alloc);
var iter = try args.argsIterator(alloc, global.args());
defer iter.deinit();
try args.parse(Options, alloc, &opts, &iter);
}
var stdout: std.fs.File = .stdout();
var stdout: std.Io.File = .stdout();
var buffer: [4096]u8 = undefined;
var stdout_writer = stdout.writer(&buffer);
var stdout_writer = stdout.writer(global.io(), &buffer);
try helpgen_actions.generate(
&stdout_writer.interface,
.plaintext,

View File

@@ -6,6 +6,7 @@ const args = @import("args.zig");
const x11_color = @import("../terminal/main.zig").x11_color;
const vaxis = @import("vaxis");
const tui = @import("tui.zig");
const global = @import("../global.zig");
pub const Options = struct {
pub fn deinit(self: Options) void {
@@ -34,7 +35,7 @@ pub fn run(alloc: Allocator) !u8 {
defer opts.deinit();
{
var iter = try args.argsIterator(alloc);
var iter = try args.argsIterator(alloc, global.args());
defer iter.deinit();
try args.parse(Options, alloc, &opts, &iter);
}
@@ -49,15 +50,14 @@ pub fn run(alloc: Allocator) !u8 {
}
}.lessThan);
// Despite being under the posix namespace, this also works on Windows as of zig 0.13.0
var stdout: std.fs.File = .stdout();
if (tui.can_pretty_print and !opts.plain and std.posix.isatty(stdout.handle)) {
var stdout: std.Io.File = .stdout();
if (tui.can_pretty_print and !opts.plain and try stdout.isTty(global.io())) {
var arena = std.heap.ArenaAllocator.init(alloc);
defer arena.deinit();
return prettyPrint(arena.allocator(), keys.items);
} else {
var buffer: [4096]u8 = undefined;
var stdout_writer = stdout.writer(&buffer);
var stdout_writer = stdout.writer(global.io(), &buffer);
const writer = &stdout_writer.interface;
for (keys.items) |name| {
const rgb = x11_color.map.get(name).?;
@@ -74,11 +74,14 @@ pub fn run(alloc: Allocator) !u8 {
}
fn prettyPrint(alloc: Allocator, keys: [][]const u8) !u8 {
var env_map = try global.environMap();
defer env_map.deinit();
// Set up vaxis
var buf: [1024]u8 = undefined;
var tty = try vaxis.Tty.init(&buf);
var tty = try vaxis.Tty.init(global.io(), &buf);
defer tty.deinit();
var vx = try vaxis.init(alloc, .{});
var vx = try vaxis.init(global.io(), alloc, &env_map, .{});
defer vx.deinit(alloc, tty.writer());
// We know we are ghostty, so let's enable mode 2027. Vaxis normally does this but you need an
@@ -97,7 +100,7 @@ fn prettyPrint(alloc: Allocator, keys: [][]const u8) !u8 {
.y_pixel = 768,
},
else => try vaxis.Tty.getWinsize(tty.fd),
else => try tty.getWinsize(),
};
try vx.resize(alloc, tty.writer(), winsize);

View File

@@ -4,6 +4,7 @@ const ArenaAllocator = std.heap.ArenaAllocator;
const Action = @import("ghostty.zig").Action;
const args = @import("args.zig");
const font = @import("../font/main.zig");
const global = @import("../global.zig");
const log = std.log.scoped(.list_fonts);
@@ -60,7 +61,7 @@ pub const Options = struct {
/// is identical to the `font-family` set of Ghostty configuration values, so
/// this can be used to debug why your desired font may not be loading.
pub fn run(alloc: Allocator) !u8 {
var iter = try args.argsIterator(alloc);
var iter = try args.argsIterator(alloc, global.args());
defer iter.deinit();
return try runArgs(alloc, &iter);
}
@@ -78,7 +79,7 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 {
// Its possible to build Ghostty without font discovery!
if (comptime font.Discover == void) {
var buffer: [1024]u8 = undefined;
var stderr_writer = std.fs.File.stderr().writer(&buffer);
var stderr_writer = std.Io.File.stderr().writer(global.io(), &buffer);
const stderr = &stderr_writer.interface;
try stderr.print(
\\Ghostty was built without a font discovery mechanism. This is a compile-time
@@ -92,7 +93,7 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 {
}
var buffer: [2048]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
var stdout_writer = std.Io.File.stdout().writer(global.io(), &buffer);
const stdout = &stdout_writer.interface;
// We'll be putting our fonts into a list categorized by family
@@ -133,7 +134,7 @@ fn runArgs(alloc_gpa: Allocator, argsIter: anytype) !u8 {
const gop = try map.getOrPut(family);
if (!gop.found_existing) {
try families.append(alloc, family);
gop.value_ptr.* = .{};
gop.value_ptr.* = .empty;
}
try gop.value_ptr.append(alloc, full_name);
}

View File

@@ -10,6 +10,7 @@ const vaxis = @import("vaxis");
const input = @import("../input.zig");
const tui = @import("tui.zig");
const Binding = input.Binding;
const global = @import("../global.zig");
pub const Options = struct {
/// If `true`, print out the default keybinds instead of the ones configured
@@ -56,7 +57,7 @@ pub fn run(alloc: Allocator) !u8 {
defer opts.deinit();
{
var iter = try args.argsIterator(alloc);
var iter = try args.argsIterator(alloc, global.args());
defer iter.deinit();
try args.parse(Options, alloc, &opts, &iter);
}
@@ -65,11 +66,11 @@ pub fn run(alloc: Allocator) !u8 {
defer config.deinit();
var buffer: [1024]u8 = undefined;
const stdout: std.fs.File = .stdout();
var stdout_writer = stdout.writer(&buffer);
const stdout: std.Io.File = .stdout();
var stdout_writer = stdout.writer(global.io(), &buffer);
const writer = &stdout_writer.interface;
if (tui.can_pretty_print and !opts.plain and stdout.isTty()) {
if (tui.can_pretty_print and !opts.plain and try stdout.isTty(global.io())) {
var arena = std.heap.ArenaAllocator.init(alloc);
defer arena.deinit();
return prettyPrint(arena.allocator(), config.keybind);
@@ -218,11 +219,14 @@ const ChordBinding = struct {
};
fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 {
var env_map = try global.environMap();
defer env_map.deinit();
// Set up vaxis
var buf: [1024]u8 = undefined;
var tty = try vaxis.Tty.init(&buf);
var tty = try vaxis.Tty.init(global.io(), &buf);
defer tty.deinit();
var vx = try vaxis.init(alloc, .{});
var vx = try vaxis.init(global.io(), alloc, &env_map, .{});
const writer = tty.writer();
defer vx.deinit(alloc, writer);
@@ -242,7 +246,7 @@ fn prettyPrint(alloc: Allocator, keybinds: Config.Keybinds) !u8 {
.y_pixel = 768,
},
else => try vaxis.Tty.getWinsize(tty.fd),
else => try tty.getWinsize(),
};
try vx.resize(alloc, writer, winsize);

View File

@@ -5,7 +5,7 @@ const Config = @import("../config/Config.zig");
const configpkg = @import("../config.zig");
const themepkg = @import("../config/theme.zig");
const tui = @import("tui.zig");
const global_state = &@import("../global.zig").state;
const global = @import("../global.zig");
const vaxis = @import("vaxis");
const zf = @import("zf");
@@ -108,7 +108,7 @@ pub fn run(gpa_alloc: std.mem.Allocator) !u8 {
defer opts.deinit();
{
var iter = try args.argsIterator(gpa_alloc);
var iter = try args.argsIterator(gpa_alloc, global.args());
defer iter.deinit();
try args.parse(Options, gpa_alloc, &opts, &iter);
}
@@ -117,15 +117,15 @@ pub fn run(gpa_alloc: std.mem.Allocator) !u8 {
const alloc = arena.allocator();
var stdout_buf: [4096]u8 = undefined;
var stdout_file: std.fs.File = .stdout();
var stdout_writer = stdout_file.writer(&stdout_buf);
var stdout_file: std.Io.File = .stdout();
var stdout_writer = stdout_file.writer(global.io(), &stdout_buf);
const stdout = &stdout_writer.interface;
var stderr_buf: [4096]u8 = undefined;
var stderr_writer = std.fs.File.stderr().writer(&stderr_buf);
var stderr_writer = std.Io.File.stderr().writer(global.io(), &stderr_buf);
const stderr = &stderr_writer.interface;
const resources_dir = global_state.resources_dir.app();
const resources_dir = global.resourcesDir().app();
if (resources_dir == null)
try stderr.print("Could not find the Ghostty resources directory. Please ensure " ++
"that Ghostty is installed correctly.\n", .{});
@@ -137,18 +137,22 @@ pub fn run(gpa_alloc: std.mem.Allocator) !u8 {
var it: themepkg.LocationIterator = .{ .arena_alloc = arena.allocator() };
while (try it.next()) |loc| {
var dir = std.fs.cwd().openDir(loc.dir, .{ .iterate = true }) catch |err| switch (err) {
var dir = std.Io.Dir.cwd().openDir(
global.io(),
loc.dir,
.{ .iterate = true },
) catch |err| switch (err) {
error.FileNotFound => continue,
else => {
std.debug.print("error trying to open {s}: {}\n", .{ loc.dir, err });
continue;
},
};
defer dir.close();
defer dir.close(global.io());
var walker = dir.iterate();
while (try walker.next()) |entry| {
while (try walker.next(global.io())) |entry| {
switch (entry.kind) {
.file, .sym_link => {
if (std.mem.eql(u8, entry.name, ".DS_Store"))
@@ -174,7 +178,7 @@ pub fn run(gpa_alloc: std.mem.Allocator) !u8 {
std.mem.sortUnstable(ThemeListElement, themes.items, {}, ThemeListElement.lessThan);
if (tui.can_pretty_print and !opts.plain and stdout_file.isTty()) {
if (tui.can_pretty_print and !opts.plain and try stdout_file.isTty(global.io())) {
try preview(gpa_alloc, themes.items, opts.color);
return 0;
}
@@ -210,14 +214,18 @@ fn writeAutoThemeFile(alloc: std.mem.Allocator, theme_name: []const u8) !void {
defer alloc.free(auto_path);
if (std.fs.path.dirname(auto_path)) |dir| {
try std.fs.cwd().makePath(dir);
try std.Io.Dir.cwd().createDirPath(global.io(), dir);
}
var f = try std.fs.createFileAbsolute(auto_path, .{ .truncate = true });
defer f.close();
var f = try std.Io.Dir.createFileAbsolute(
global.io(),
auto_path,
.{ .truncate = true },
);
defer f.close(global.io());
var buf: [128]u8 = undefined;
var w = f.writer(&buf);
var w = f.writer(global.io(), &buf);
try w.interface.print("theme = {s}\n", .{theme_name});
try w.interface.flush();
}
@@ -256,13 +264,16 @@ const Preview = struct {
theme_filter: ColorScheme,
buf: []u8,
) !*Preview {
var env_map = try global.environMap();
defer env_map.deinit();
const self = try allocator.create(Preview);
self.* = .{
.allocator = allocator,
.should_quit = false,
.tty = try .init(buf),
.vx = try vaxis.init(allocator, .{}),
.tty = try .init(global.io(), buf),
.vx = try vaxis.init(global.io(), allocator, &env_map, .{}),
.mouse = null,
.themes = themes,
.filtered = try .initCapacity(allocator, themes.len),
@@ -290,18 +301,12 @@ const Preview = struct {
}
pub fn run(self: *Preview) !void {
var loop: vaxis.Loop(Event) = .{
.tty = &self.tty,
.vaxis = &self.vx,
};
try loop.init();
try loop.start();
var loop: vaxis.Loop(Event) = .init(global.io(), &self.tty, &self.vx);
const writer = self.tty.writer();
try self.vx.enterAltScreen(writer);
try self.vx.setTitle(writer, "👻 Ghostty Theme Preview 👻");
try self.vx.queryTerminal(writer, 1 * std.time.ns_per_s);
try self.vx.queryTerminal(writer, .fromSeconds(1));
try self.vx.setMouseMode(writer, true);
if (self.vx.caps.color_scheme_updates)
try self.vx.subscribeToColorSchemeUpdates(writer);
@@ -311,8 +316,8 @@ const Preview = struct {
defer arena.deinit();
const alloc = arena.allocator();
loop.pollEvent();
while (loop.tryEvent()) |event| {
try loop.pollEvent();
while (try loop.tryEvent()) |event| {
try self.update(event, alloc);
}
try self.draw(alloc);
@@ -366,7 +371,12 @@ const Preview = struct {
if (!shouldIncludeTheme(self.theme_filter, theme_config)) continue;
theme.rank = zf.rank(theme.theme, tokens.items, .{
.to_lower = true,
// NOTE: Changed from ".to_lower = true" (the option was
// renamed). I think this is the correct analog for case
// insensitive ranking (which is what I'm guessing
// ".to_lower = true" implies, but this comment serves as a
// hint if there's a regression.
.case_sensitive = false,
.plain = true,
});
if (theme.rank != null) try self.filtered.append(self.allocator, i);
@@ -620,13 +630,21 @@ const Preview = struct {
self.down(1);
}
if (theme_list.hasMouse(mouse)) |_| {
// NOTE: mouse co-ordinates can be negative (see
// https://github.com/rockorager/libvaxis/pull/276).
// Working around it in this case, but putting this here in
// case there are issues.
if (mouse.button == .left and mouse.type == .release) {
const selection = self.window + mouse.row;
const selection: usize = selection: {
var window: i32 = @min(self.window, std.math.maxInt(i32));
window += mouse.row;
break :selection @max(0, window);
};
if (selection < self.filtered.items.len) {
self.current = selection;
}
}
highlight = mouse.row;
highlight = @max(0, mouse.row);
}
}
}

View File

@@ -7,8 +7,12 @@ const args = @import("args.zig");
const diagnostics = @import("diagnostics.zig");
const lib = @import("../lib/main.zig");
const homedir = @import("../os/homedir.zig");
const global = @import("../global.zig");
pub const Options = struct {
/// We store an I/O implementation to make maintenance easier.
_io: std.Io,
/// This is set by the CLI parser for deinit.
_arena: ?ArenaAllocator = null,
@@ -26,8 +30,18 @@ pub const Options = struct {
/// there is a "normal" config setting on the cli.
_diagnostics: diagnostics.DiagnosticList = .{},
pub const ParseManuallyHookError = error{InvalidValue} ||
homedir.ExpandError ||
std.Io.Dir.RealPathFileAllocError ||
Allocator.Error;
/// Manual parse hook, collect all of the arguments after `+new-window`.
pub fn parseManuallyHook(self: *Options, alloc: Allocator, arg: []const u8, iter: anytype) (error{InvalidValue} || homedir.ExpandError || std.fs.Dir.RealPathAllocError || Allocator.Error)!bool {
pub fn parseManuallyHook(
self: *Options,
alloc: Allocator,
arg: []const u8,
iter: anytype,
) ParseManuallyHookError!bool {
var e_seen: bool = std.mem.eql(u8, arg, "-e");
// Include the argument that triggered the manual parse hook.
@@ -50,7 +64,12 @@ pub const Options = struct {
return false;
}
fn checkArg(self: *Options, alloc: Allocator, arg: []const u8) (error{InvalidValue} || homedir.ExpandError || std.fs.Dir.RealPathAllocError || Allocator.Error)!?[:0]const u8 {
const CheckArgError = error{InvalidValue} ||
homedir.ExpandError ||
std.Io.Dir.RealPathFileAllocError ||
Allocator.Error;
fn checkArg(self: *Options, alloc: Allocator, arg: []const u8) CheckArgError!?[:0]const u8 {
if (lib.cutPrefix(u8, arg, "--class=")) |rest| {
self.class = try alloc.dupeZ(u8, std.mem.trim(u8, rest, &std.ascii.whitespace));
return null;
@@ -60,11 +79,15 @@ pub const Options = struct {
const stripped = std.mem.trim(u8, rest, &std.ascii.whitespace);
if (std.mem.eql(u8, stripped, "home")) return try alloc.dupeZ(u8, arg);
if (std.mem.eql(u8, stripped, "inherit")) return try alloc.dupeZ(u8, arg);
const cwd: std.fs.Dir = std.fs.cwd();
const cwd: std.Io.Dir = .cwd();
var expandhome_buf: [std.fs.max_path_bytes]u8 = undefined;
const expanded = try homedir.expandHome(stripped, &expandhome_buf);
const expanded = expanded: {
var environ_map = try global.environMap();
defer environ_map.deinit();
break :expanded try homedir.expandHome(&environ_map, stripped, &expandhome_buf);
};
var realpath_buf: [std.fs.max_path_bytes]u8 = undefined;
const realpath = try cwd.realpath(expanded, &realpath_buf);
const realpath = realpath_buf[0..try cwd.realPathFile(self._io, expanded, &realpath_buf)];
self._working_directory_seen = true;
return try std.fmt.allocPrintSentinel(alloc, "--working-directory={s}", .{realpath}, 0);
}
@@ -147,11 +170,11 @@ pub const Options = struct {
///
/// Available since: 1.2.0
pub fn run(alloc: Allocator) !u8 {
var iter = try args.argsIterator(alloc);
var iter = try args.argsIterator(alloc, global.args());
defer iter.deinit();
var buffer: [1024]u8 = undefined;
var stderr_writer = std.fs.File.stderr().writer(&buffer);
var stderr_writer = std.Io.File.stderr().writer(global.io(), &buffer);
const stderr = &stderr_writer.interface;
const result = runArgs(alloc, &iter, stderr);
@@ -164,7 +187,7 @@ fn runArgs(
argsIter: anytype,
stderr: *std.Io.Writer,
) !u8 {
var opts: Options = .{};
var opts: Options = .{ ._io = global.io() };
defer opts.deinit();
args.parse(Options, alloc_gpa, &opts, argsIter) catch |err| switch (err) {
@@ -195,11 +218,16 @@ fn runArgs(
if (!opts._working_directory_seen) {
const alloc = opts._arena.?.allocator();
const cwd: std.fs.Dir = std.fs.cwd();
const cwd: std.Io.Dir = .cwd();
var buf: [std.fs.max_path_bytes]u8 = undefined;
const wd = try cwd.realpath(".", &buf);
const wd = buf[0..try cwd.realPathFile(global.io(), ".", &buf)];
// This should be inserted at the beginning of the list, just in case `-e` was used.
try opts._arguments.insert(alloc, 0, try std.fmt.allocPrintSentinel(alloc, "--working-directory={s}", .{wd}, 0));
try opts._arguments.insert(alloc, 0, try std.fmt.allocPrintSentinel(
alloc,
"--working-directory={s}",
.{wd},
0,
));
}
var arena = ArenaAllocator.init(alloc_gpa);

View File

@@ -5,6 +5,7 @@ const Action = @import("ghostty.zig").Action;
const configpkg = @import("../config.zig");
const Config = configpkg.Config;
const Pager = @import("Pager.zig");
const global = @import("../global.zig");
pub const Options = struct {
/// If true, do not load the user configuration, only load the defaults.
@@ -66,7 +67,7 @@ pub fn run(alloc: Allocator) !u8 {
defer opts.deinit();
{
var iter = try args.argsIterator(alloc);
var iter = try args.argsIterator(alloc, global.args());
defer iter.deinit();
try args.parse(Options, alloc, &opts, &iter);
}
@@ -81,7 +82,7 @@ pub fn run(alloc: Allocator) !u8 {
.docs = opts.docs,
};
var pager: Pager = if (!opts.@"no-pager") .init(alloc) else .{};
var pager: Pager = if (!opts.@"no-pager") .init() else .{};
defer pager.deinit();
var buffer: [4096]u8 = undefined;
const writer = pager.writer(&buffer);

View File

@@ -7,6 +7,7 @@ const diagnostics = @import("diagnostics.zig");
const font = @import("../font/main.zig");
const configpkg = @import("../config.zig");
const Config = configpkg.Config;
const global = @import("../global.zig");
pub const Options = struct {
/// This is set by the CLI parser for deinit.
@@ -62,15 +63,15 @@ pub const Options = struct {
/// style. Valid options are `text` and `emoji`. If unset, the presentation
/// style of a codepoint will be inferred from the Unicode standard.
pub fn run(alloc: Allocator) !u8 {
var iter = try args.argsIterator(alloc);
var iter = try args.argsIterator(alloc, global.args());
defer iter.deinit();
var stdout_buffer: [1024]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&stdout_buffer);
var stdout_writer = std.Io.File.stdout().writer(global.io(), &stdout_buffer);
const stdout = &stdout_writer.interface;
var stderr_buffer: [1024]u8 = undefined;
var stderr_writer = std.fs.File.stdout().writer(&stderr_buffer);
var stderr_writer = std.Io.File.stdout().writer(global.io(), &stderr_buffer);
const stderr = &stderr_writer.interface;
const result = runArgs(

View File

@@ -10,6 +10,7 @@ const Allocator = std.mem.Allocator;
const internal_os = @import("../../os/main.zig");
const xdg = internal_os.xdg;
const Entry = @import("Entry.zig");
const global = @import("../../global.zig");
// 512KB - sufficient for approximately 10k entries
const MAX_CACHE_SIZE = 512 * 1024;
@@ -26,8 +27,11 @@ pub fn defaultPath(
alloc: Allocator,
program: []const u8,
) ![]const u8 {
var environ_map = try global.environMap();
defer environ_map.deinit();
const state_dir: []const u8 = xdg.state(
alloc,
&environ_map,
.{ .subdir = program },
) catch |err| return switch (err) {
error.OutOfMemory => error.OutOfMemory,
@@ -41,7 +45,7 @@ pub fn defaultPath(
/// This removes the cache file from disk, effectively clearing all cached
/// SSH terminfo entries.
pub fn clear(self: DiskCache) !void {
std.fs.cwd().deleteFile(self.path) catch |err| switch (err) {
std.Io.Dir.cwd().deleteFile(global.io(), self.path) catch |err| switch (err) {
error.FileNotFound => {},
else => return err,
};
@@ -59,36 +63,43 @@ pub fn add(
// Create cache directory if needed
if (std.fs.path.dirname(self.path)) |dir| {
std.fs.cwd().makePath(dir) catch |err| switch (err) {
std.Io.Dir.cwd().createDirPath(global.io(), dir) catch |err| switch (err) {
error.PathAlreadyExists => {},
else => return err,
};
}
// Open or create cache file with secure permissions
const file = std.fs.createFileAbsolute(self.path, .{
const file = std.Io.Dir.createFileAbsolute(global.io(), self.path, .{
.read = true,
.truncate = false,
.mode = 0o600,
.permissions = if (builtin.os.tag != .windows and std.posix.mode_t != u0)
.fromMode(0o600)
else
.default_file,
}) catch |err| switch (err) {
error.PathAlreadyExists => blk: {
const existing_file = try std.fs.openFileAbsolute(
const existing_file = try std.Io.Dir.openFileAbsolute(
global.io(),
self.path,
.{ .mode = .read_write },
);
errdefer existing_file.close();
errdefer existing_file.close(global.io());
try fixupPermissions(existing_file);
break :blk existing_file;
},
else => return err,
};
defer file.close();
defer file.close(global.io());
// Lock
// Causes a compile failure in the Zig std library on Windows, see:
// https://github.com/ziglang/zig/issues/18430
if (comptime builtin.os.tag != .windows) _ = file.tryLock(.exclusive) catch return error.CacheLocked;
defer if (comptime builtin.os.tag != .windows) file.unlock();
if (comptime builtin.os.tag != .windows) _ = file.tryLock(
global.io(),
.exclusive,
) catch return error.CacheLocked;
defer if (comptime builtin.os.tag != .windows) file.unlock(global.io());
var entries = try readEntries(alloc, file);
defer deinitEntries(alloc, &entries);
@@ -125,21 +136,25 @@ pub fn remove(
if (!isValidCacheKey(key)) return error.InvalidCacheKey;
// Open our file
const file = std.fs.openFileAbsolute(
const file = std.Io.Dir.openFileAbsolute(
global.io(),
self.path,
.{ .mode = .read_write },
) catch |err| switch (err) {
error.FileNotFound => return false,
else => return err,
};
defer file.close();
defer file.close(global.io());
try fixupPermissions(file);
// Lock
// Causes a compile failure in the Zig std library on Windows, see:
// https://github.com/ziglang/zig/issues/18430
if (comptime builtin.os.tag != .windows) _ = file.tryLock(.exclusive) catch return error.CacheLocked;
defer if (comptime builtin.os.tag != .windows) file.unlock();
if (comptime builtin.os.tag != .windows) _ = file.tryLock(
global.io(),
.exclusive,
) catch return error.CacheLocked;
defer if (comptime builtin.os.tag != .windows) file.unlock(global.io());
// Read existing entries
var entries = try readEntries(alloc, file);
@@ -165,28 +180,32 @@ pub fn prune(
alloc: Allocator,
max_age_s: u64,
) !usize {
const file = std.fs.openFileAbsolute(
const file = std.Io.Dir.openFileAbsolute(
global.io(),
self.path,
.{ .mode = .read_write },
) catch |err| switch (err) {
error.FileNotFound => return 0,
else => return err,
};
defer file.close();
defer file.close(global.io());
try fixupPermissions(file);
// Lock
// Causes a compile failure in the Zig std library on Windows, see:
// https://github.com/ziglang/zig/issues/18430
if (comptime builtin.os.tag != .windows) _ = file.tryLock(.exclusive) catch return error.CacheLocked;
defer if (comptime builtin.os.tag != .windows) file.unlock();
if (comptime builtin.os.tag != .windows) _ = file.tryLock(
global.io(),
.exclusive,
) catch return error.CacheLocked;
defer if (comptime builtin.os.tag != .windows) file.unlock(global.io());
// Read existing entries
var entries = try readEntries(alloc, file);
defer deinitEntries(alloc, &entries);
// Drop expired entries from the map, then persist what remains.
const now = std.time.timestamp();
const now = std.Io.Timestamp.now(global.io(), .real).toSeconds();
var expired: std.ArrayList([]const u8) = .empty;
defer expired.deinit(alloc);
var iter = entries.iterator();
@@ -215,14 +234,15 @@ pub fn contains(
if (!isValidCacheKey(key)) return error.InvalidCacheKey;
// Open our file
const file = std.fs.openFileAbsolute(
const file = std.Io.Dir.openFileAbsolute(
global.io(),
self.path,
.{},
) catch |err| switch (err) {
error.FileNotFound => return false,
else => return err,
};
defer file.close();
defer file.close(global.io());
// Read existing entries
var entries = try readEntries(alloc, file);
@@ -231,15 +251,15 @@ pub fn contains(
return entries.contains(key);
}
fn fixupPermissions(file: std.fs.File) !void {
fn fixupPermissions(file: std.Io.File) !void {
// Windows does not support chmod
if (comptime builtin.os.tag == .windows) return;
// Ensure file has correct permissions (readable/writable by
// owner only)
const stat = try file.stat();
if (stat.mode & 0o777 != 0o600) {
try file.chmod(0o600);
const stat = try file.stat(global.io());
if (stat.permissions.toMode() & 0o777 != 0o600) {
try file.setPermissions(global.io(), .fromMode(0o600));
}
}
@@ -250,22 +270,27 @@ fn writeCacheFile(
const cache_dir = std.fs.path.dirname(self.path) orelse return error.InvalidCachePath;
const cache_basename = std.fs.path.basename(self.path);
var dir = try std.fs.cwd().openDir(cache_dir, .{});
defer dir.close();
var dir = try std.Io.Dir.cwd().openDir(global.io(), cache_dir, .{});
defer dir.close(global.io());
var buf: [1024]u8 = undefined;
var atomic_file = try dir.atomicFile(cache_basename, .{
.mode = 0o600,
.write_buffer = &buf,
var atomic_file = try dir.createFileAtomic(global.io(), cache_basename, .{
.permissions = if (builtin.os.tag != .windows and std.posix.mode_t != u0)
.fromMode(0o600)
else
.default_file,
.replace = true,
});
defer atomic_file.deinit();
defer atomic_file.deinit(global.io());
var file_writer = atomic_file.file.writer(global.io(), &buf);
var iter = entries.iterator();
while (iter.next()) |kv| {
try kv.value_ptr.format(&atomic_file.file_writer.interface);
try kv.value_ptr.format(&file_writer.interface);
}
try atomic_file.finish();
try file_writer.flush();
try atomic_file.replace(global.io());
}
/// List all entries in the cache.
@@ -276,14 +301,15 @@ pub fn list(
alloc: Allocator,
) !std.StringHashMap(Entry) {
// Open our file
const file = std.fs.openFileAbsolute(
const file = std.Io.Dir.openFileAbsolute(
global.io(),
self.path,
.{},
) catch |err| switch (err) {
error.FileNotFound => return .init(alloc),
else => return err,
};
defer file.close();
defer file.close(global.io());
return readEntries(alloc, file);
}
@@ -306,9 +332,9 @@ pub fn deinitEntries(
fn readEntries(
alloc: Allocator,
file: std.fs.File,
file: std.Io.File,
) !std.StringHashMap(Entry) {
var reader = file.reader(&.{});
var reader = file.reader(global.io(), &.{});
const content = try reader.interface.allocRemaining(
alloc,
.limited(MAX_CACHE_SIZE),
@@ -392,7 +418,7 @@ fn isValidHost(host: []const u8) bool {
// We also accept valid IP addresses. In practice, IPv4 addresses are also
// considered valid hostnames due to their overlapping syntax, so we can
// simplify this check to be IPv6-specific.
if (std.net.Address.parseIp6(host, 0)) |_| {
if (std.Io.net.IpAddress.parseIp6(host, 0)) |_| {
return true;
} else |_| {
return false;
@@ -428,12 +454,12 @@ test "disk cache clear" {
defer tmp.cleanup();
var buf: [4096]u8 = undefined;
{
var file = try tmp.dir.createFile("cache", .{});
defer file.close();
var file_writer = file.writer(&buf);
var file = try tmp.dir.createFile(testing.io, "cache", .{});
defer file.close(testing.io);
var file_writer = file.writer(testing.io, &buf);
try file_writer.interface.writeAll("HELLO!");
}
const path = try tmp.dir.realpathAlloc(alloc, "cache");
const path = try tmp.dir.realPathFileAlloc(testing.io, "cache", alloc);
defer alloc.free(path);
// Setup our cache
@@ -443,7 +469,7 @@ test "disk cache clear" {
// Verify the file is gone
try testing.expectError(
error.FileNotFound,
tmp.dir.openFile("cache", .{}),
tmp.dir.openFile(testing.io, "cache", .{}),
);
}
@@ -456,21 +482,21 @@ test "disk cache operations" {
defer tmp.cleanup();
var buf: [4096]u8 = undefined;
{
var file = try tmp.dir.createFile("cache", .{});
defer file.close();
var file_writer = file.writer(&buf);
var file = try tmp.dir.createFile(testing.io, "cache", .{});
defer file.close(testing.io);
var file_writer = file.writer(testing.io, &buf);
const writer = &file_writer.interface;
try writer.writeAll("HELLO!");
try writer.flush();
}
const path = try tmp.dir.realpathAlloc(alloc, "cache");
const path = try tmp.dir.realPathFileAlloc(testing.io, "cache", alloc);
defer alloc.free(path);
// Setup our cache. Adding the same key twice exercises both the new
// and existing-entry paths.
const cache: DiskCache = .{ .path = path };
try cache.add(alloc, "example.com", std.time.timestamp());
try cache.add(alloc, "example.com", std.time.timestamp());
try cache.add(alloc, "example.com", std.Io.Timestamp.now(testing.io, .real).toSeconds());
try cache.add(alloc, "example.com", std.Io.Timestamp.now(testing.io, .real).toSeconds());
try testing.expect(try cache.contains(alloc, "example.com"));
// List
@@ -482,7 +508,7 @@ test "disk cache operations" {
try testing.expect(try cache.remove(alloc, "example.com"));
try testing.expect(!try cache.remove(alloc, "example.com"));
try testing.expect(!(try cache.contains(alloc, "example.com")));
try cache.add(alloc, "example.com", std.time.timestamp());
try cache.add(alloc, "example.com", std.Io.Timestamp.now(testing.io, .real).toSeconds());
}
test "disk cache cleans up temp files" {
@@ -492,19 +518,19 @@ test "disk cache cleans up temp files" {
var tmp = testing.tmpDir(.{ .iterate = true });
defer tmp.cleanup();
const tmp_path = try tmp.dir.realpathAlloc(alloc, ".");
const tmp_path = try tmp.dir.realPathFileAlloc(testing.io, ".", alloc);
defer alloc.free(tmp_path);
const cache_path = try std.fs.path.join(alloc, &.{ tmp_path, "cache" });
defer alloc.free(cache_path);
const cache: DiskCache = .{ .path = cache_path };
try cache.add(alloc, "example.com", std.time.timestamp());
try cache.add(alloc, "example.org", std.time.timestamp());
try cache.add(alloc, "example.com", std.Io.Timestamp.now(testing.io, .real).toSeconds());
try cache.add(alloc, "example.org", std.Io.Timestamp.now(testing.io, .real).toSeconds());
// Verify only the cache file exists and no temp files left behind
var count: usize = 0;
var iter = tmp.dir.iterate();
while (try iter.next()) |entry| {
while (try iter.next(testing.io)) |entry| {
count += 1;
try testing.expectEqualStrings("cache", entry.name);
}
@@ -514,10 +540,11 @@ test "disk cache cleans up temp files" {
test "disk cache prune" {
const testing = std.testing;
const alloc = testing.allocator;
const io = testing.io;
var tmp = testing.tmpDir(.{});
defer tmp.cleanup();
const tmp_path = try tmp.dir.realpathAlloc(alloc, ".");
const tmp_path = try tmp.dir.realPathFileAlloc(io, ".", alloc);
defer alloc.free(tmp_path);
const cache_path = try std.fs.path.join(alloc, &.{ tmp_path, "cache" });
defer alloc.free(cache_path);
@@ -527,7 +554,7 @@ test "disk cache prune" {
// Back-date one entry an hour old and one 100 days old.
const day = std.time.s_per_day;
const hour = std.time.s_per_hour;
const now = std.time.timestamp();
const now = std.Io.Timestamp.now(testing.io, .real).toSeconds();
try cache.add(alloc, "recent.com", now - hour);
try cache.add(alloc, "old.com", now - 100 * day);
@@ -547,11 +574,12 @@ test "disk cache prune" {
test "disk cache prune missing file" {
const testing = std.testing;
const alloc = testing.allocator;
const io = testing.io;
var tmp = testing.tmpDir(.{});
defer tmp.cleanup();
const tmp_path = try tmp.dir.realpathAlloc(alloc, ".");
const tmp_path = try tmp.dir.realPathFileAlloc(io, ".", alloc);
defer alloc.free(tmp_path);
const cache_path = try std.fs.path.join(alloc, &.{ tmp_path, "cache" });
defer alloc.free(cache_path);
@@ -563,6 +591,7 @@ test "disk cache prune missing file" {
test "disk cache reads duplicate keys" {
const testing = std.testing;
const alloc = testing.allocator;
const io = testing.io;
var tmp = testing.tmpDir(.{});
defer tmp.cleanup();
@@ -571,16 +600,16 @@ test "disk cache reads duplicate keys" {
// key with the updated entry and ensure (via testing.allocator) that
// we don't double-free or leak.
{
var file = try tmp.dir.createFile("cache", .{});
defer file.close();
var file = try tmp.dir.createFile(io, "cache", .{});
defer file.close(io);
var buf: [256]u8 = undefined;
var file_writer = file.writer(&buf);
var file_writer = file.writer(io, &buf);
try file_writer.interface.writeAll(
"example.com|100|xterm-ghostty\nexample.com|200|xterm-newer\n",
);
try file_writer.interface.flush();
}
const path = try tmp.dir.realpathAlloc(alloc, "cache");
const path = try tmp.dir.realPathFileAlloc(io, "cache", alloc);
defer alloc.free(path);
const cache: DiskCache = .{ .path = path };
@@ -602,10 +631,10 @@ test "disk cache reads survive allocation failure" {
// Exercise a populated cache containing a duplicate key to ensure
// that we hit all of the possible allocation behaviors below.
{
var file = try tmp.dir.createFile("cache", .{});
defer file.close();
var file = try tmp.dir.createFile(testing.io, "cache", .{});
defer file.close(testing.io);
var buf: [256]u8 = undefined;
var file_writer = file.writer(&buf);
var file_writer = file.writer(testing.io, &buf);
try file_writer.interface.writeAll(
"a.com|100|xterm-ghostty\n" ++
"b.com|100|xterm-ghostty\n" ++
@@ -614,7 +643,7 @@ test "disk cache reads survive allocation failure" {
);
try file_writer.interface.flush();
}
const path = try tmp.dir.realpathAlloc(testing.allocator, "cache");
const path = try tmp.dir.realPathFileAlloc(testing.io, "cache", testing.allocator);
defer testing.allocator.free(path);
const cache: DiskCache = .{ .path = path };
@@ -647,7 +676,7 @@ test "disk cache add survives allocation failure" {
var tmp = testing.tmpDir(.{});
defer tmp.cleanup();
const tmp_path = try tmp.dir.realpathAlloc(testing.allocator, ".");
const tmp_path = try tmp.dir.realPathFileAlloc(testing.io, ".", testing.allocator);
defer testing.allocator.free(tmp_path);
const path = try std.fs.path.join(testing.allocator, &.{ tmp_path, "cache" });
defer testing.allocator.free(path);
@@ -660,7 +689,7 @@ test "disk cache add survives allocation failure" {
// from a clean cache file.
var fail_index: usize = 0;
while (true) : (fail_index += 1) {
std.fs.cwd().deleteFile(path) catch {};
std.Io.Dir.cwd().deleteFile(testing.io, path) catch {};
var failing = std.testing.FailingAllocator.init(
testing.allocator,
.{ .fail_index = fail_index },

View File

@@ -1,4 +1,5 @@
const std = @import("std");
const builtin = @import("builtin");
const Allocator = std.mem.Allocator;
const ArenaAllocator = std.heap.ArenaAllocator;
const cli_args = @import("args.zig");
@@ -7,6 +8,7 @@ const Action = @import("ghostty.zig").Action;
const DiskCache = @import("ssh_cache.zig").DiskCache;
const internal_os = @import("../os/main.zig");
const ghostty_terminfo = @import("../terminfo/main.zig").ghostty;
const global = @import("../global.zig");
const log = std.log.scoped(.ssh);
@@ -181,14 +183,14 @@ pub fn run(alloc_gpa: Allocator) !u8 {
defer opts.deinit();
{
var iter = try cli_args.argsIterator(alloc_gpa);
var iter = try cli_args.argsIterator(alloc_gpa, global.args());
defer iter.deinit();
try cli_args.parse(Options, alloc_gpa, &opts, &iter);
}
var stderr_buffer: [1024]u8 = undefined;
var stderr_file: std.fs.File = .stderr();
var stderr_writer = stderr_file.writer(&stderr_buffer);
var stderr_file: std.Io.File = .stderr();
var stderr_writer = stderr_file.writer(global.io(), &stderr_buffer);
const stderr = &stderr_writer.interface;
// Any diagnostic from the arg parser is an unknown flag or bad
@@ -294,7 +296,7 @@ fn runInner(
});
verbosePrint(opts, stderr, "exec: {f}", .{Joined{ .items = argv }});
const exit_code = childExec(alloc, argv) catch |err| {
const exit_code = childExec(argv) catch |err| {
try stderr.print("Error: failed to run {s}: {}\n", .{ argv[0], err });
return 1;
};
@@ -302,7 +304,10 @@ fn runInner(
// Attempt to cache (if needed) on a successful ssh execution.
if (exit_code == 0) if (session.to_cache) |entry| {
if (entry.cache.add(alloc, entry.dest, std.time.timestamp())) |_| {
if (entry.cache.add(alloc, entry.dest, std.Io.Timestamp.now(
global.io(),
.real,
).toSeconds())) |_| {
verbosePrint(opts, stderr, "cache: wrote {s}", .{entry.dest});
} else |err| {
log.debug("cache add failed for '{s}': {}", .{ entry.dest, err });
@@ -370,7 +375,7 @@ const Joined = struct {
fn checkExit(term: std.process.Child.Term, label: []const u8) error{ChildFailed}!void {
switch (term) {
.Exited => |rc| if (rc != 0) {
.exited => |rc| if (rc != 0) {
log.warn("{s} exited with non-zero status: {d}", .{ label, rc });
return error.ChildFailed;
},
@@ -393,10 +398,11 @@ fn resolveDestination(
&.{ ssh, "-G" },
args,
}) catch return null;
const result = std.process.Child.run(.{
.allocator = alloc,
.argv = argv,
}) catch |err| {
const result = std.process.run(
alloc,
global.io(),
.{ .argv = argv },
) catch |err| {
log.warn("ssh -G spawn failed: {}", .{err});
return null;
};
@@ -491,23 +497,23 @@ fn installRemoteTerminfo(
});
verbosePrint(opts, stderr, "exec: {f}", .{Joined{ .items = argv }});
var child: std.process.Child = .init(argv, alloc);
child.stdin_behavior = .Pipe;
child.stdout_behavior = .Ignore;
child.stderr_behavior = if (opts.verbose) .Inherit else .Ignore;
child.spawn() catch |err| {
var child = std.process.spawn(global.io(), .{
.argv = argv,
.stdin = .pipe,
.stdout = .ignore,
.stderr = if (opts.verbose) .inherit else .ignore,
}) catch |err| {
log.warn("terminfo install spawn failed: {}", .{err});
return error.InstallFailed;
};
if (child.stdin) |stdin| {
stdin.writeAll(terminfo) catch {};
stdin.close();
stdin.writeStreamingAll(global.io(), terminfo) catch {};
stdin.close(global.io());
child.stdin = null;
}
const term = child.wait() catch |err| {
const term = child.wait(global.io()) catch |err| {
log.warn("terminfo install wait failed: {}", .{err});
return error.InstallFailed;
};
@@ -515,23 +521,24 @@ fn installRemoteTerminfo(
}
/// Returns `128 + signum` for signal-killed children, matching shell convention.
fn childExec(alloc: Allocator, argv: []const []const u8) !u8 {
var child: std.process.Child = .init(argv, alloc);
child.stdin_behavior = .Inherit;
child.stdout_behavior = .Inherit;
child.stderr_behavior = .Inherit;
fn childExec(argv: []const []const u8) !u8 {
var child = try std.process.spawn(global.io(), .{
.argv = argv,
.stdin = .inherit,
.stdout = .inherit,
.stderr = .inherit,
});
try child.spawn();
const term = try child.wait();
const term = try child.wait(global.io());
return switch (term) {
.Exited => |rc| rc,
.Signal => |sig| @as(u8, 128) + @as(u8, @intCast(@min(sig, 127))),
.Stopped, .Unknown => 1,
.exited => |rc| rc,
.signal => |sig| @as(u8, 128) + @as(u8, @intCast(@min(@intFromEnum(sig), 127))),
.stopped, .unknown => 1,
};
}
fn parseTestArgs(alloc: Allocator, opts: *Options, line: []const u8) !void {
var iter = try std.process.ArgIteratorGeneral(.{}).init(alloc, line);
var iter = try std.process.Args.IteratorGeneral(.{}).init(alloc, line);
defer iter.deinit();
try cli_args.parse(Options, alloc, opts, &iter);
}

View File

@@ -2,6 +2,7 @@ const std = @import("std");
const fs = std.fs;
const Allocator = std.mem.Allocator;
const args = @import("args.zig");
const global = @import("../global.zig");
const Action = @import("ghostty.zig").Action;
const Duration = @import("../config.zig").Config.Duration;
pub const Entry = @import("ssh-cache/Entry.zig");
@@ -64,13 +65,13 @@ pub fn run(alloc_gpa: Allocator) !u8 {
defer opts.deinit();
var stdout_buffer: [1024]u8 = undefined;
var stdout_file: std.fs.File = .stdout();
var stdout_writer = stdout_file.writer(&stdout_buffer);
var stdout_file: std.Io.File = .stdout();
var stdout_writer = stdout_file.writer(global.io(), &stdout_buffer);
const stdout = &stdout_writer.interface;
var stderr_buffer: [1024]u8 = undefined;
var stderr_file: std.fs.File = .stderr();
var stderr_writer = stderr_file.writer(&stderr_buffer);
var stderr_file: std.Io.File = .stderr();
var stderr_writer = stderr_file.writer(global.io(), &stderr_buffer);
const stderr = &stderr_writer.interface;
// The cache is queried by a positional destination (`user@host` or a
@@ -81,7 +82,7 @@ pub fn run(alloc_gpa: Allocator) !u8 {
var query: ?[]const u8 = null;
var flags: std.ArrayList([]const u8) = .empty;
{
var iter = try args.argsIterator(alloc_gpa);
var iter = try args.argsIterator(alloc_gpa, global.args());
defer iter.deinit();
while (iter.next()) |arg| {
const is_host_flag = std.mem.startsWith(u8, arg, "--host=");
@@ -174,7 +175,11 @@ pub fn runInner(
}
if (opts.add) |dest| {
cache.add(alloc, dest, std.time.timestamp()) catch |err| switch (err) {
cache.add(
alloc,
dest,
std.Io.Timestamp.now(global.io(), .real).toSeconds(),
) catch |err| switch (err) {
error.InvalidCacheKey => {
try stderr.print(
"Error: Invalid destination '{s}' (expected hostname or user@hostname)\n",
@@ -295,7 +300,7 @@ fn listEntries(
widest = @max(widest, entry.hostname.len);
}
const now = std.time.timestamp();
const now = std.Io.Timestamp.now(global.io(), .real).toSeconds();
for (items.items) |entry| {
try writer.print("{s}", .{entry.hostname});
try writer.splatByteAll(' ', widest - entry.hostname.len + 2);

View File

@@ -2,6 +2,7 @@ const std = @import("std");
const Allocator = std.mem.Allocator;
const Action = @import("../cli.zig").ghostty.Action;
const apprt = @import("../apprt.zig");
const global = @import("../global.zig");
pub const Options = struct {
/// If set, connect to a custom instance of Ghostty.
@@ -39,7 +40,7 @@ pub const Options = struct {
/// Available since: 1.4.0
pub fn run(alloc: Allocator) !u8 {
var buf: [256]u8 = undefined;
var stderr_writer = std.fs.File.stderr().writer(&buf);
var stderr_writer = std.Io.File.stderr().writer(global.io(), &buf);
const stderr = &stderr_writer.interface;
if (apprt.App.performIpc(

View File

@@ -3,6 +3,7 @@ const Allocator = std.mem.Allocator;
const args = @import("args.zig");
const Action = @import("ghostty.zig").Action;
const Config = @import("../config.zig").Config;
const global = @import("../global.zig");
pub const Options = struct {
/// The path of the config file to validate. If this isn't specified,
@@ -34,13 +35,13 @@ pub fn run(alloc: std.mem.Allocator) !u8 {
defer opts.deinit();
{
var iter = try args.argsIterator(alloc);
var iter = try args.argsIterator(alloc, global.args());
defer iter.deinit();
try args.parse(Options, alloc, &opts, &iter);
}
var buffer: [1024]u8 = undefined;
var stdout_writer = std.fs.File.stdout().writer(&buffer);
var stdout_writer = std.Io.File.stdout().writer(global.io(), &buffer);
const stdout = &stdout_writer.interface;
const result = runInner(alloc, opts, stdout);
try stdout_writer.end();
@@ -58,7 +59,11 @@ fn runInner(
// If a config path is passed, validate it, otherwise validate default configs
if (opts.@"config-file") |config_path| {
var buf: [std.fs.max_path_bytes]u8 = undefined;
const abs_path = try std.fs.cwd().realpath(config_path, &buf);
const abs_path = buf[0..try std.Io.Dir.cwd().realPathFile(
global.io(),
config_path,
&buf,
)];
try cfg.loadFile(alloc, abs_path);
try cfg.loadRecursiveFiles(alloc);
} else {

View File

@@ -6,6 +6,7 @@ const build_config = @import("../build_config.zig");
const internal_os = @import("../os/main.zig");
const xev = @import("../global.zig").xev;
const renderer = @import("../renderer.zig");
const global = @import("../global.zig");
const gtk_version = @import("../apprt/gtk/gtk_version.zig");
const adw_version = @import("../apprt/gtk/adw_version.zig");
@@ -16,11 +17,14 @@ pub const Options = struct {};
/// either `+version` or `--version`.
pub fn run(alloc: Allocator) !u8 {
var buffer: [1024]u8 = undefined;
const stdout_file: std.fs.File = .stdout();
var stdout_writer = stdout_file.writer(&buffer);
const stdout_file: std.Io.File = .stdout();
var stdout_writer = stdout_file.writer(global.io(), &buffer);
var environ_map = try global.environMap();
defer environ_map.deinit();
const stdout = &stdout_writer.interface;
const tty = stdout_file.isTty();
const tty = try stdout_file.isTty(global.io());
if (tty) if (build_config.version.build) |commit_hash| {
try stdout.print(
@@ -48,7 +52,7 @@ pub fn run(alloc: Allocator) !u8 {
defer if (kernel_info) |k| alloc.free(k);
try stdout.print(" - kernel version: {s}\n", .{kernel_info orelse "Kernel information unavailable"});
}
try stdout.print(" - desktop env : {t}\n", .{internal_os.desktopEnvironment()});
try stdout.print(" - desktop env : {t}\n", .{internal_os.desktopEnvironment(&environ_map)});
try stdout.print(" - GTK version :\n", .{});
try stdout.print(" build : {f}\n", .{gtk_version.comptime_version});
try stdout.print(" runtime : {f}\n", .{gtk_version.getRuntimeVersion()});