From 936914f3e0addd6a9fb19e826166e79927926046 Mon Sep 17 00:00:00 2001 From: Nathan Bourgeois Date: Fri, 15 May 2026 19:18:24 -0400 Subject: [PATCH] Move environment map to a new class, load once --- examples/file_browser.zig | 7 +- examples/theming.zig | 2 +- src/components/file_picker.zig | 25 ++++--- src/core/context.zig | 13 ++-- src/core/environment.zig | 122 +++++++++++++++++++++++++++++++++ src/core/program.zig | 31 ++++----- src/root.zig | 1 + src/style/color.zig | 36 +++++----- src/style/theme.zig | 10 ++- src/terminal/terminal.zig | 82 ++++++---------------- tests/program_tests.zig | 6 +- tests/theme_tests.zig | 12 +--- 12 files changed, 214 insertions(+), 133 deletions(-) create mode 100644 src/core/environment.zig diff --git a/examples/file_browser.zig b/examples/file_browser.zig index 9f3efbe..90235aa 100644 --- a/examples/file_browser.zig +++ b/examples/file_browser.zig @@ -16,9 +16,10 @@ const Model = struct { pub fn init(self: *Model, ctx: *zz.Context) zz.Cmd(Msg) { self.file_picker = zz.components.FilePicker.init(ctx.persistent_allocator); self.file_picker.height = ctx.height -| 10; + self.file_picker.setHomePath(ctx.home_dir); // Start at home directory - self.file_picker.navigateHome(ctx.io, ctx.environ_map) catch { + self.file_picker.navigateHome(ctx.io) catch { self.file_picker.navigate(ctx.io, "/") catch {}; }; @@ -34,7 +35,7 @@ const Model = struct { .char => |c| switch (c) { 'q' => return .quit, else => { - const selected = self.file_picker.handleKey(ctx.io, ctx.environ_map, k) catch false; + const selected = self.file_picker.handleKey(ctx.io, k) catch false; if (selected) { self.loadPreview(ctx.io); } @@ -42,7 +43,7 @@ const Model = struct { }, .escape => return .quit, else => { - const selected = self.file_picker.handleKey(ctx.io, ctx.environ_map, k) catch false; + const selected = self.file_picker.handleKey(ctx.io, k) catch false; if (selected) { self.loadPreview(ctx.io); } diff --git a/examples/theming.zig b/examples/theming.zig index e4f8584..fb0da8c 100644 --- a/examples/theming.zig +++ b/examples/theming.zig @@ -15,7 +15,7 @@ const Model = struct { }; pub fn init(self: *Model, ctx: *zz.Context) zz.Cmd(Msg) { - self.tm = zz.ThemeManager.init(ctx.environ_map); + self.tm = zz.ThemeManager.init(ctx.is_dark_background); self.progress_val = 35; // Also set the theme on the context so components can read it ctx.setTheme(self.tm.current.palette); diff --git a/src/components/file_picker.zig b/src/components/file_picker.zig index 56720ad..dcea3f7 100644 --- a/src/components/file_picker.zig +++ b/src/components/file_picker.zig @@ -30,6 +30,7 @@ pub const FilePicker = struct { dir_only: bool, file_only: bool, allowed_extensions: ?[]const []const u8, + home_path: []const u8, // Styling dir_style: style_mod.Style, @@ -76,6 +77,7 @@ pub const FilePicker = struct { .dir_only = false, .file_only = false, .allowed_extensions = null, + .home_path = defaultHomePath(), .dir_style = blk: { var s = style_mod.Style{}; s = s.bold(true); @@ -225,15 +227,13 @@ pub const FilePicker = struct { self.y_offset = 0; } - /// Navigate to home directory using the supplied environment. - pub fn navigateHome(self: *FilePicker, io: std.Io, environ_map: *const std.process.Environ.Map) !void { - if (comptime builtin.os.tag == .windows) { - const home = environ_map.get("USERPROFILE") orelse "C:\\"; - try self.navigate(io, home); - } else { - const home = environ_map.get("HOME") orelse "/"; - try self.navigate(io, home); - } + pub fn setHomePath(self: *FilePicker, home_path: []const u8) void { + self.home_path = if (home_path.len > 0) home_path else defaultHomePath(); + } + + /// Navigate to the configured home directory. + pub fn navigateHome(self: *FilePicker, io: std.Io) !void { + try self.navigate(io, self.home_path); } /// Move cursor up @@ -295,7 +295,6 @@ pub const FilePicker = struct { pub fn handleKey( self: *FilePicker, io: std.Io, - environ_map: *const std.process.Environ.Map, key: keys.KeyEvent, ) !bool { if (!self.focused) return false; @@ -314,7 +313,7 @@ pub const FilePicker = struct { 'j' => self.cursorDown(), 'k' => self.cursorUp(), 'h' => self.showHidden(io), - '~' => try self.navigateHome(io, environ_map), + '~' => try self.navigateHome(io), else => {}, } }, @@ -330,6 +329,10 @@ pub const FilePicker = struct { self.navigate(io, path_copy) catch {}; } + fn defaultHomePath() []const u8 { + return if (comptime builtin.os.tag == .windows) "C:\\" else "/"; + } + fn ensureVisible(self: *FilePicker) void { if (self.cursor < self.y_offset) { self.y_offset = self.cursor; diff --git a/src/core/context.zig b/src/core/context.zig index 608fb53..28e5d50 100644 --- a/src/core/context.zig +++ b/src/core/context.zig @@ -9,6 +9,7 @@ const color_mod = @import("../style/color.zig"); const unicode_mod = @import("../unicode.zig"); const Logger = @import("log.zig").Logger; const theme_mod = @import("../style/theme.zig"); +const Environment = @import("environment.zig").Environment; /// Runtime context passed to init, update, and view functions pub const Context = struct { @@ -18,8 +19,8 @@ pub const Context = struct { /// Persistent allocator for model state (not reset between frames) persistent_allocator: std.mem.Allocator, - /// Process environment for env-driven detection (color, multiplexer, locale). - environ_map: *const std.process.Environ.Map, + /// Home directory captured from the process environment at startup. + home_dir: []const u8, /// Asynchronous I/O facilities (file, network, time, sleep). io: std.Io, @@ -80,14 +81,14 @@ pub const Context = struct { allocator: std.mem.Allocator, persistent_allocator: std.mem.Allocator, io: std.Io, - environ_map: *const std.process.Environ.Map, + environment: *const Environment, ) Context { - const profile = color_mod.ColorProfile.detect(environ_map); + const profile = environment.color_profile; return .{ .allocator = allocator, .persistent_allocator = persistent_allocator, .io = io, - .environ_map = environ_map, + .home_dir = environment.home_dir, .width = 80, .height = 24, .frame = 0, @@ -96,7 +97,7 @@ pub const Context = struct { .true_color = profile.supportsTrueColor(), .color_256 = profile.supports256(), .color_profile = profile, - .is_dark_background = color_mod.hasDarkBackground(environ_map), + .is_dark_background = environment.is_dark_background, .unicode_width_strategy = unicode_mod.getWidthStrategy(), .terminal_mode_2027 = false, .kitty_text_sizing = false, diff --git a/src/core/environment.zig b/src/core/environment.zig new file mode 100644 index 0000000..ed0135e --- /dev/null +++ b/src/core/environment.zig @@ -0,0 +1,122 @@ +//! Process environment values captured at program startup. + +const std = @import("std"); +const builtin = @import("builtin"); +const color_mod = @import("../style/color.zig"); +const unicode_mod = @import("../unicode.zig"); + +pub const Environment = struct { + term: []const u8 = "", + term_program: []const u8 = "", + lc_terminal: []const u8 = "", + color_term: []const u8 = "", + color_fg_bg: []const u8 = "", + term_features: []const u8 = "", + home_dir: []const u8 = defaultHomeDir(), + no_color: bool = false, + has_tmux: bool = false, + has_zellij: bool = false, + has_kitty_window: bool = false, + color_profile: color_mod.ColorProfile = .ansi, + is_dark_background: bool = true, + unicode_width_override: ?unicode_mod.WidthStrategy = null, + + pub fn fromEnvMap(environ_map: *const std.process.Environ.Map) Environment { + const term = environ_map.get("TERM") orelse ""; + const term_program = environ_map.get("TERM_PROGRAM") orelse ""; + const lc_terminal = environ_map.get("LC_TERMINAL") orelse ""; + const color_term = environ_map.get("COLORTERM") orelse ""; + const color_fg_bg = environ_map.get("COLORFGBG") orelse ""; + const term_features = environ_map.get("TERM_FEATURES") orelse ""; + const home_dir = homeDirFromEnvMap(environ_map); + const no_color = environ_map.get("NO_COLOR") != null; + + return .{ + .term = term, + .term_program = term_program, + .lc_terminal = lc_terminal, + .color_term = color_term, + .color_fg_bg = color_fg_bg, + .term_features = term_features, + .home_dir = home_dir, + .no_color = no_color, + .has_tmux = envValuePresent(environ_map, "TMUX"), + .has_zellij = envValuePresent(environ_map, "ZELLIJ"), + .has_kitty_window = envValuePresent(environ_map, "KITTY_WINDOW_ID"), + .color_profile = color_mod.ColorProfile.detect(.{ + .no_color = no_color, + .color_term = color_term, + .term = term, + }), + .is_dark_background = color_mod.hasDarkBackground(color_fg_bg), + .unicode_width_override = parseUnicodeWidthOverride(environ_map.get("ZZ_UNICODE_WIDTH") orelse ""), + }; + } + + pub fn isInsideMultiplexer(self: *const Environment) bool { + return self.has_tmux or self.has_zellij or self.termContains("screen"); + } + + pub fn isKnownUnicodeWidthTerminal(self: *const Environment) bool { + return self.termProgramEquals("WezTerm") or + self.termProgramEquals("iTerm.app") or + self.termContains("wezterm") or + self.termContains("ghostty"); + } + + pub fn looksLikeKittyTerminal(self: *const Environment) bool { + return self.has_kitty_window or self.termContains("kitty"); + } + + pub fn looksLikeIterm2Terminal(self: *const Environment) bool { + return self.termProgramEquals("iTerm.app") or self.lcTerminalEquals("iTerm2"); + } + + pub fn looksLikeSixelTerminal(self: *const Environment) bool { + return self.termContains("sixel") or + self.termContains("mlterm") or + self.termContains("yaft") or + self.termContains("contour"); + } + + pub fn termContains(self: *const Environment, needle: []const u8) bool { + return std.mem.indexOf(u8, self.term, needle) != null; + } + + pub fn termProgramEquals(self: *const Environment, expected: []const u8) bool { + return std.ascii.eqlIgnoreCase(self.term_program, expected); + } + + pub fn lcTerminalEquals(self: *const Environment, expected: []const u8) bool { + return std.ascii.eqlIgnoreCase(self.lc_terminal, expected); + } + + fn homeDirFromEnvMap(environ_map: *const std.process.Environ.Map) []const u8 { + if (comptime builtin.os.tag == .windows) { + if (environ_map.get("USERPROFILE")) |home| { + if (home.len > 0) return home; + } + } else { + if (environ_map.get("HOME")) |home| { + if (home.len > 0) return home; + } + } + return defaultHomeDir(); + } + + fn defaultHomeDir() []const u8 { + return if (comptime builtin.os.tag == .windows) "C:\\" else "/"; + } + + fn envValuePresent(environ_map: *const std.process.Environ.Map, name: []const u8) bool { + const value = environ_map.get(name) orelse return false; + return value.len > 0; + } + + fn parseUnicodeWidthOverride(raw: []const u8) ?unicode_mod.WidthStrategy { + if (std.ascii.eqlIgnoreCase(raw, "unicode")) return .unicode; + if (std.ascii.eqlIgnoreCase(raw, "legacy")) return .legacy_wcwidth; + if (std.ascii.eqlIgnoreCase(raw, "auto")) return null; + return null; + } +}; diff --git a/src/core/program.zig b/src/core/program.zig index 67e2130..81a6922 100644 --- a/src/core/program.zig +++ b/src/core/program.zig @@ -12,6 +12,7 @@ const message = @import("message.zig"); const command = @import("command.zig"); const Logger = @import("log.zig").Logger; const unicode = @import("../unicode.zig"); +const Environment = @import("environment.zig").Environment; pub const Cmd = command.Cmd; pub const Msg = message; @@ -47,7 +48,7 @@ pub fn Program(comptime Model: type) type { return struct { allocator: std.mem.Allocator, io: std.Io, - environ_map: *const std.process.Environ.Map, + environment: Environment, arena: std.heap.ArenaAllocator, model: Model, terminal: ?Terminal, @@ -78,7 +79,7 @@ pub fn Program(comptime Model: type) type { const Self = @This(); - /// Initialize the program + /// Initialize the program. pub fn init( allocator: std.mem.Allocator, io: std.Io, @@ -87,7 +88,7 @@ pub fn Program(comptime Model: type) type { return initWithOptions(allocator, io, environ_map, .{}); } - /// Initialize with custom options + /// Initialize with custom options. pub fn initWithOptions( allocator: std.mem.Allocator, io: std.Io, @@ -96,16 +97,14 @@ pub fn Program(comptime Model: type) type { ) Self { const arena = std.heap.ArenaAllocator.init(allocator); const clock_epoch = std.Io.Clock.Timestamp.now(io, .boot); - const self = Self{ + var self = Self{ .allocator = allocator, .io = io, - .environ_map = environ_map, + .environment = .fromEnvMap(environ_map), .arena = arena, .model = undefined, .terminal = null, - // `self` is returned by value, so don't capture an arena allocator here. - // It would point at this function's stack copy and dangle after return. - .context = Context.init(allocator, allocator, io, environ_map), + .context = undefined, .options = options, .running = false, .clock_epoch = clock_epoch, @@ -122,6 +121,10 @@ pub fn Program(comptime Model: type) type { .filter = null, }; + // `self` is returned by value, so don't capture an arena allocator here. + // It would point at this function's stack copy and dangle after return. + self.context = Context.init(allocator, allocator, io, &self.environment); + return self; } @@ -179,7 +182,7 @@ pub fn Program(comptime Model: type) type { } // Initialize terminal - self.terminal = try Terminal.init(self.io, self.environ_map, .{ + self.terminal = try Terminal.init(self.io, &self.environment, .{ .alt_screen = self.options.alt_screen, .hide_cursor = !self.options.cursor, .mouse = self.options.mouse, @@ -394,20 +397,12 @@ pub fn Program(comptime Model: type) type { if (self.options.unicode_width_strategy) |forced| { return forced; } - if (envUnicodeWidthOverride(self.environ_map)) |from_env| { + if (self.environment.unicode_width_override) |from_env| { return from_env; } return detected; } - fn envUnicodeWidthOverride(environ_map: *const std.process.Environ.Map) ?unicode.WidthStrategy { - const raw = environ_map.get("ZZ_UNICODE_WIDTH") orelse return null; - if (std.ascii.eqlIgnoreCase(raw, "unicode")) return .unicode; - if (std.ascii.eqlIgnoreCase(raw, "legacy")) return .legacy_wcwidth; - if (std.ascii.eqlIgnoreCase(raw, "auto")) return null; - return null; - } - fn processMouseEvent(self: *Self, mouse_event: keyboard.MouseEvent) ?UserCmd { if (@hasField(UserMsg, "mouse")) { const user_msg = UserMsg{ .mouse = mouse_event }; diff --git a/src/root.zig b/src/root.zig index 2275f17..54750a5 100644 --- a/src/root.zig +++ b/src/root.zig @@ -52,6 +52,7 @@ pub const program = @import("core/program.zig"); pub const Program = program.Program; pub const Cmd = program.Cmd; pub const command = @import("core/command.zig"); +pub const Environment = @import("core/environment.zig").Environment; pub const async_task = @import("core/async_task.zig"); pub const AsyncRunner = async_task.AsyncRunner; pub const SubProgram = @import("core/sub_program.zig").SubProgram; diff --git a/src/style/color.zig b/src/style/color.zig index 5ad9eb5..8704f30 100644 --- a/src/style/color.zig +++ b/src/style/color.zig @@ -249,27 +249,31 @@ pub const ColorProfile = enum { ansi256, true_color, - /// Detect terminal color profile from the supplied environment. - pub fn detect(environ_map: *const std.process.Environ.Map) ColorProfile { + pub const DetectionHints = struct { + no_color: bool = false, + color_term: []const u8 = "", + term: []const u8 = "", + }; + + /// Detect terminal color profile from values captured at startup. + pub fn detect(hints: DetectionHints) ColorProfile { if (comptime builtin.os.tag == .windows) { // Windows Terminal supports true color VT sequences return .true_color; } - if (environ_map.get("NO_COLOR")) |_| { + if (hints.no_color) { return .ascii; } - if (environ_map.get("COLORTERM")) |ct| { - if (std.mem.eql(u8, ct, "truecolor") or std.mem.eql(u8, ct, "24bit")) { - return .true_color; - } + if (std.mem.eql(u8, hints.color_term, "truecolor") or + std.mem.eql(u8, hints.color_term, "24bit")) + { + return .true_color; } - if (environ_map.get("TERM")) |term| { - if (std.mem.indexOf(u8, term, "256color") != null) { - return .ansi256; - } + if (std.mem.indexOf(u8, hints.term, "256color") != null) { + return .ansi256; } return .ansi; @@ -291,16 +295,16 @@ pub const ColorProfile = enum { } }; -/// Detect if terminal has a dark background using the supplied environment. -pub fn hasDarkBackground(environ_map: *const std.process.Environ.Map) bool { +/// Detect if terminal has a dark background from the COLORFGBG value captured at startup. +pub fn hasDarkBackground(color_fg_bg: []const u8) bool { if (comptime builtin.os.tag == .windows) { return true; } - if (environ_map.get("COLORFGBG")) |val| { + if (color_fg_bg.len > 0) { // Format: "foreground;background" - if (std.mem.lastIndexOfScalar(u8, val, ';')) |idx| { - const bg_str = val[idx + 1 ..]; + if (std.mem.lastIndexOfScalar(u8, color_fg_bg, ';')) |idx| { + const bg_str = color_fg_bg[idx + 1 ..]; const bg_num = std.fmt.parseInt(u8, bg_str, 10) catch return true; // Low numbers typically mean dark background return bg_num < 8; diff --git a/src/style/theme.zig b/src/style/theme.zig index 4657e38..966ac72 100644 --- a/src/style/theme.zig +++ b/src/style/theme.zig @@ -306,9 +306,8 @@ pub const ThemeManager = struct { is_dark: bool, palette_index: usize, - /// Initialize with dark/light background detected from `environ_map`. - pub fn init(environ_map: *const std.process.Environ.Map) ThemeManager { - const is_dark = @import("color.zig").hasDarkBackground(environ_map); + /// Initialize with the dark/light background detected at startup. + pub fn init(is_dark: bool) ThemeManager { const palette = AdaptivePalette.default.resolve(is_dark); return .{ .current = .fromPalette(palette), @@ -317,9 +316,8 @@ pub const ThemeManager = struct { }; } - /// Initialize with a specific palette and `environ_map`-detected dark/light hint. - pub fn initWithPalette(environ_map: *const std.process.Environ.Map, palette: Palette) ThemeManager { - const is_dark = @import("color.zig").hasDarkBackground(environ_map); + /// Initialize with a specific palette and the dark/light background detected at startup. + pub fn initWithPalette(is_dark: bool, palette: Palette) ThemeManager { return .{ .current = .fromPalette(palette), .is_dark = is_dark, diff --git a/src/terminal/terminal.zig b/src/terminal/terminal.zig index 0e07e58..ff36a4e 100644 --- a/src/terminal/terminal.zig +++ b/src/terminal/terminal.zig @@ -7,6 +7,7 @@ const builtin = @import("builtin"); pub const ansi = @import("ansi.zig"); pub const screen = @import("screen.zig"); const unicode = @import("../unicode.zig"); +const Environment = @import("../core/environment.zig").Environment; // Platform-specific implementation const is_wasm = builtin.os.tag == .wasi or builtin.cpu.arch == .wasm32 or builtin.cpu.arch == .wasm64; @@ -261,7 +262,7 @@ pub const Config = struct { /// Terminal abstraction pub const Terminal = struct { io: std.Io, - environ_map: *const std.process.Environ.Map, + environment: *const Environment, state: platform.State, config: Config, stdin: std.Io.File, @@ -271,12 +272,12 @@ pub const Terminal = struct { unicode_width_caps: UnicodeWidthCapabilities = .{}, image_caps: ImageCapabilities = .{}, - pub fn init(io: std.Io, environ_map: *const std.process.Environ.Map, config: Config) !Terminal { + pub fn init(io: std.Io, environment: *const Environment, config: Config) !Terminal { var state = platform.State.init(); var term: Terminal = if (is_wasm) .{ .io = io, - .environ_map = environ_map, + .environment = environment, .state = state, .config = config, .stdin = undefined, @@ -296,7 +297,7 @@ pub const Terminal = struct { break :blk .{ .io = io, - .environ_map = environ_map, + .environment = environment, .state = state, .config = config, .stdin = stdin, @@ -1032,25 +1033,25 @@ pub const Terminal = struct { return; } - const term_features = self.environ_map.get("TERM_FEATURES") orelse ""; + const env = self.environment; + const term_features = env.term_features; - const kitty_candidate = looksLikeKittyTerminal(self.environ_map) or - envVarEquals(self.environ_map, "TERM_PROGRAM", "WezTerm") or - envVarContains(self.environ_map, "TERM", "wezterm") or - envVarContains(self.environ_map, "TERM", "ghostty"); - const iterm_candidate = looksLikeIterm2Terminal(self.environ_map) or - envVarEquals(self.environ_map, "TERM_PROGRAM", "WezTerm"); - const in_multiplexer = isInsideMultiplexer(self.environ_map); + const kitty_candidate = env.looksLikeKittyTerminal() or + env.termProgramEquals("WezTerm") or + env.termContains("wezterm") or + env.termContains("ghostty"); + const iterm_candidate = env.looksLikeIterm2Terminal() or env.termProgramEquals("WezTerm"); + const in_multiplexer = env.isInsideMultiplexer(); var kitty = false; var iterm = iterm_candidate or termFeaturesContain(term_features, "F"); - var sixel = looksLikeSixelTerminal(self.environ_map) or termFeaturesContain(term_features, "Sx"); + var sixel = env.looksLikeSixelTerminal() or termFeaturesContain(term_features, "Sx"); if (kitty_candidate) { kitty = self.queryKittyGraphicsSupport() catch false; // Keep an env fallback only outside multiplexers where probe failures are uncommon. if (!kitty and !in_multiplexer) { - kitty = envVarExists(self.environ_map, "KITTY_WINDOW_ID"); + kitty = env.has_kitty_window; } } @@ -1187,8 +1188,8 @@ pub const Terminal = struct { .tmux => .tmux, .dcs => .dcs, .auto => blk: { - if (envVarExists(self.environ_map, "TMUX")) break :blk .tmux; - if (envVarContains(self.environ_map, "TERM", "screen")) break :blk .dcs; + if (self.environment.has_tmux) break :blk .tmux; + if (self.environment.termContains("screen")) break :blk .dcs; break :blk .none; }, }; @@ -1595,7 +1596,7 @@ pub const Terminal = struct { } fn selectWidthStrategy(self: *const Terminal) unicode.WidthStrategy { - if (isInsideMultiplexer(self.environ_map)) { + if (self.environment.isInsideMultiplexer()) { return .legacy_wcwidth; } @@ -1607,7 +1608,7 @@ pub const Terminal = struct { return .unicode; } - if (isKnownUnicodeWidthTerminal(self.environ_map)) { + if (self.environment.isKnownUnicodeWidthTerminal()) { return .unicode; } @@ -1615,7 +1616,7 @@ pub const Terminal = struct { } fn queryKittyTextSizingSupport(self: *Terminal) !bool { - if (!looksLikeKittyTerminal(self.environ_map)) return false; + if (!self.environment.looksLikeKittyTerminal()) return false; const cpr = "\x1b[6n"; // CR, CPR, draw 2-cell space via kitty OSC 66 width-only, CPR. @@ -1684,10 +1685,6 @@ pub const Terminal = struct { return null; } - fn isInsideMultiplexer(environ_map: *const std.process.Environ.Map) bool { - return envVarExists(environ_map, "TMUX") or envVarExists(environ_map, "ZELLIJ") or envVarContains(environ_map, "TERM", "screen"); - } - fn drainInput(self: *Terminal) void { var buf: [128]u8 = undefined; while (true) { @@ -1738,30 +1735,6 @@ pub const Terminal = struct { return null; } - fn isKnownUnicodeWidthTerminal(environ_map: *const std.process.Environ.Map) bool { - // Terminals known to use grapheme-aware width by default. - return envVarEquals(environ_map, "TERM_PROGRAM", "WezTerm") or - envVarEquals(environ_map, "TERM_PROGRAM", "iTerm.app") or - envVarContains(environ_map, "TERM", "wezterm") or - envVarContains(environ_map, "TERM", "ghostty"); - } - - fn looksLikeKittyTerminal(environ_map: *const std.process.Environ.Map) bool { - return envVarExists(environ_map, "KITTY_WINDOW_ID") or envVarContains(environ_map, "TERM", "kitty"); - } - - fn looksLikeIterm2Terminal(environ_map: *const std.process.Environ.Map) bool { - return envVarEquals(environ_map, "TERM_PROGRAM", "iTerm.app") or - envVarEquals(environ_map, "LC_TERMINAL", "iTerm2"); - } - - fn looksLikeSixelTerminal(environ_map: *const std.process.Environ.Map) bool { - return envVarContains(environ_map, "TERM", "sixel") or - envVarContains(environ_map, "TERM", "mlterm") or - envVarContains(environ_map, "TERM", "yaft") or - envVarContains(environ_map, "TERM", "contour"); - } - fn isLikelyFullSixelSequence(bytes: []const u8) bool { if (bytes.len == 0) return false; return std.mem.startsWith(u8, bytes, ansi.DCS) or bytes[0] == 0x90; @@ -1797,21 +1770,6 @@ pub const Terminal = struct { return true; } - fn envVarExists(environ_map: *const std.process.Environ.Map, name: []const u8) bool { - const value = environ_map.get(name) orelse return false; - return value.len > 0; - } - - fn envVarEquals(environ_map: *const std.process.Environ.Map, name: []const u8, expected: []const u8) bool { - const value = environ_map.get(name) orelse return false; - return std.ascii.eqlIgnoreCase(value, expected); - } - - fn envVarContains(environ_map: *const std.process.Environ.Map, name: []const u8, needle: []const u8) bool { - const value = environ_map.get(name) orelse return false; - return std.mem.indexOf(u8, value, needle) != null; - } - /// Buffered writer that exposes a `std.Io.Writer` interface and drains to /// the terminal's output sink (stdout file, or the wasm host on wasm). pub const Writer = struct { diff --git a/tests/program_tests.zig b/tests/program_tests.zig index b50ef10..784d57d 100644 --- a/tests/program_tests.zig +++ b/tests/program_tests.zig @@ -23,7 +23,11 @@ const DummyModel = struct { test "Program.init context allocator is stable before start and can be rebound to arena" { var env_map: std.process.Environ.Map = .init(testing.allocator); defer env_map.deinit(); - var program = zz.Program(DummyModel).init(testing.allocator, testing.io, &env_map); + var program = zz.Program(DummyModel).init( + testing.allocator, + testing.io, + &env_map, + ); defer program.deinit(); const backing_ptr = @intFromPtr(testing.allocator.ptr); diff --git a/tests/theme_tests.zig b/tests/theme_tests.zig index ac3e8bb..4e47bd2 100644 --- a/tests/theme_tests.zig +++ b/tests/theme_tests.zig @@ -83,9 +83,7 @@ test "Theme boldStyleWith creates bold inline style" { } test "ThemeManager init and cycle" { - var env_map: std.process.Environ.Map = .init(testing.allocator); - defer env_map.deinit(); - var tm = zz.ThemeManager.init(&env_map); + var tm = zz.ThemeManager.init(true); // Should start at index 0 try testing.expectEqualStrings("Default Dark", tm.currentName()); @@ -102,9 +100,7 @@ test "ThemeManager init and cycle" { } test "ThemeManager setBuiltinByIndex" { - var env_map: std.process.Environ.Map = .init(testing.allocator); - defer env_map.deinit(); - var tm = zz.ThemeManager.init(&env_map); + var tm = zz.ThemeManager.init(true); tm.setBuiltinByIndex(4); // Dracula try testing.expectEqualStrings("Dracula", tm.currentName()); @@ -112,9 +108,7 @@ test "ThemeManager setBuiltinByIndex" { } test "ThemeManager setPalette with custom palette" { - var env_map: std.process.Environ.Map = .init(testing.allocator); - defer env_map.deinit(); - var tm = zz.ThemeManager.init(&env_map); + var tm = zz.ThemeManager.init(true); const custom = zz.Palette{ .primary = .red,