From ae206b2f8957bd85e9f5e7900d6230b16258dc5c Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Thu, 6 Jul 2023 14:14:55 -0700 Subject: [PATCH] termio: fish shell integration injection --- src/termio/Exec.zig | 24 ++++++++++- src/termio/shell_integration.zig | 70 ++++++++++++++++++++++++++++++++ 2 files changed, 93 insertions(+), 1 deletion(-) create mode 100644 src/termio/shell_integration.zig diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 1a69d7d60..aa3e4df0a 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -21,6 +21,7 @@ const apprt = @import("../apprt.zig"); const fastmem = @import("../fastmem.zig"); const internal_os = @import("../os/main.zig"); const configpkg = @import("../config.zig"); +const shell_integration = @import("shell_integration.zig"); const log = std.log.scoped(.io_exec); @@ -594,11 +595,32 @@ const Subprocess = struct { else null; + // The execution path + const final_path = if (internal_os.isFlatpak()) args[0] else path; + + // Setup our shell integration, if we can. + const shell_integrated: ?shell_integration.Shell = shell: { + const dir = resources_dir orelse break :shell null; + break :shell try shell_integration.setup( + dir, + final_path, + &env, + ); + }; + if (shell_integrated) |shell| { + log.info( + "shell integration automatically injected shell={}", + .{shell}, + ); + } else { + log.warn("shell could not be detected, no automatic shell integration will be injected", .{}); + } + return .{ .arena = arena, .env = env, .cwd = cwd, - .path = if (internal_os.isFlatpak()) args[0] else path, + .path = final_path, .args = args, .grid_size = opts.grid_size, .screen_size = opts.screen_size, diff --git a/src/termio/shell_integration.zig b/src/termio/shell_integration.zig new file mode 100644 index 000000000..c941965b5 --- /dev/null +++ b/src/termio/shell_integration.zig @@ -0,0 +1,70 @@ +const std = @import("std"); +const EnvMap = std.process.EnvMap; + +const log = std.log.scoped(.shell_integration); + +/// Shell types we support +pub const Shell = enum { + fish, +}; + +/// Setup the command execution environment for automatic +/// integrated shell integration. This returns true if shell +/// integration was successful. False could mean many things: +/// the shell type wasn't detected, etc. +pub fn setup( + resource_dir: []const u8, + command_path: []const u8, + env: *EnvMap, +) !?Shell { + const exe = std.fs.path.basename(command_path); + if (std.mem.eql(u8, "fish", exe)) { + try setupFish(resource_dir, env); + return .fish; + } + + return null; +} + +/// Setup the fish automatic shell integration. This works by +/// modify XDG_DATA_DIRS to include the resource directory. +/// Fish will automatically load configuration in XDG_DATA_DIRS +/// "fish/vendor_conf.d/*.fish". +fn setupFish( + resource_dir: []const u8, + env: *EnvMap, +) !void { + var path_buf: [std.fs.MAX_PATH_BYTES]u8 = undefined; + + // Get our path to the shell integration directory. + const integ_dir = try std.fmt.bufPrint( + &path_buf, + "{s}/shell-integration", + .{resource_dir}, + ); + + // Set an env var so we can remove this from XDG_DATA_DIRS later. + // This happens in the shell integration config itself. We do this + // so that our modifications don't interfere with other commands. + try env.put("GHOSTTY_FISH_DIR", integ_dir); + + if (env.get("XDG_DATA_DIRS")) |old| { + // We have an old value, We need to prepend our value to it. + + // We use a 4K buffer to hold our XDG_DATA_DIR value. The stack + // on macOS is at least 512K and Linux is 8MB or more. So this + // should fit. If the user has a XDG_DATA_DIR value that is longer + // than this then it will fail... and we will cross that bridge + // when we actually get there. This avoids us needing an allocator. + var buf: [4096]u8 = undefined; + const prepended = try std.fmt.bufPrint( + &buf, + "{s}{c}{s}", + .{ integ_dir, std.fs.path.delimiter, old }, + ); + try env.put("XDG_DATA_DIRS", prepended); + } else { + // No XDG_DATA_DIRS set, we just set it our desired value. + try env.put("XDG_DATA_DIRS", integ_dir); + } +}