diff --git a/include/ghostty.h b/include/ghostty.h index 2feb35ad99..9d69f5d056 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -402,6 +402,7 @@ typedef struct { float font_size; const char* working_directory; const char* command; + void* _arena; } ghostty_surface_config_s; typedef void (*ghostty_runtime_wakeup_cb)(void*); diff --git a/src/Surface.zig b/src/Surface.zig index bb351a9c48..b1093de5f0 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -125,6 +125,13 @@ config: DerivedConfig, /// This is used to determine if we need to confirm, hold open, etc. child_exited: bool = false, +/// The kind of surface. +pub const Kind = enum { + split, + tab, + window, +}; + /// The effect of an input event. This can be used by callers to take /// the appropriate action after an input event. For example, key /// input can be forwarded to the OS for further processing if it diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 37caf7d0f4..148456fbab 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -8,6 +8,7 @@ const std = @import("std"); const builtin = @import("builtin"); const assert = std.debug.assert; const Allocator = std.mem.Allocator; +const ArenaAllocator = std.heap.ArenaAllocator; const objc = @import("objc"); const apprt = @import("../apprt.zig"); const font = @import("../font/main.zig"); @@ -329,6 +330,17 @@ pub const Surface = struct { /// the "wait-after-command" option is also automatically set to true, /// since this is used for scripting. command: [*:0]const u8 = "", + + _arena: ?*anyopaque = null, + + pub fn deinit(self: *Options) void { + if (self._arena) |ptr| { + const arena: *ArenaAllocator = @ptrCast(@alignCast(ptr)); + const alloc = arena.child_allocator; + arena.deinit(); + alloc.destroy(arena); + } + } }; /// This is the key event sent for ghostty_surface_key. @@ -473,7 +485,7 @@ pub const Surface = struct { return; }; - const options = self.newSurfaceOptions(); + const options = try self.newSurfaceOptions(.split); func(self.userdata, direction, options); } @@ -1024,7 +1036,7 @@ pub const Surface = struct { return; }; - const options = self.newSurfaceOptions(); + const options = try self.newSurfaceOptions(.tab); func(self.userdata, options); } @@ -1034,7 +1046,7 @@ pub const Surface = struct { return; }; - const options = self.newSurfaceOptions(); + const options = try self.newSurfaceOptions(.window); func(self.userdata, options); } @@ -1065,14 +1077,44 @@ pub const Surface = struct { func(self.userdata, width, height); } - fn newSurfaceOptions(self: *const Surface) apprt.Surface.Options { + fn newSurfaceOptions(self: *const Surface, kind: CoreSurface.Kind) !apprt.Surface.Options { const font_size: f32 = font_size: { if (!self.app.config.@"window-inherit-font-size") break :font_size 0; break :font_size self.core_surface.font_size.points; }; + const working_directory: [*:0]const u8, const arena: ?*ArenaAllocator = dir: { + const prev = self.app.core_app.focusedSurface(); + if (prev) |p| { + const inherit_pwd: bool = switch (kind) { + .split => self.app.config.@"window-inherit-working-directory".split, + .tab => self.app.config.@"window-inherit-working-directory".tab, + .window => self.app.config.@"window-inherit-working-directory".window, + }; + if (inherit_pwd) { + const app_alloc = self.app.core_app.alloc; + + var arena = try app_alloc.create(ArenaAllocator); + errdefer app_alloc.destroy(arena); + + arena.* = ArenaAllocator.init(app_alloc); + errdefer arena.deinit(); + + const arena_alloc = arena.allocator(); + + if (try p.pwd(app_alloc)) |pwd| { + defer app_alloc.free(pwd); + break :dir .{ try arena_alloc.dupeZ(u8, pwd), arena }; + } + } + } + break :dir .{ "", null }; + }; + return .{ .font_size = font_size, + .working_directory = working_directory, + ._arena = arena, }; } @@ -1499,6 +1541,7 @@ pub const CAPI = struct { app: *App, opts: *const apprt.Surface.Options, ) !*Surface { + defer @constCast(opts).deinit(); return try app.newSurface(opts.*); } diff --git a/src/apprt/surface.zig b/src/apprt/surface.zig index ae3ba050ab..384cce0a2f 100644 --- a/src/apprt/surface.zig +++ b/src/apprt/surface.zig @@ -87,20 +87,5 @@ pub const Mailbox = struct { /// after the surface is initialized. pub fn newConfig(app: *const App, config: *const Config) !Config { // Create a shallow clone - var copy = config.shallowClone(app.alloc); - - // Our allocator is our config's arena - const alloc = copy._arena.?.allocator(); - - // Get our previously focused surface for some inherited values. - const prev = app.focusedSurface(); - if (prev) |p| { - if (config.@"window-inherit-working-directory") { - if (try p.pwd(alloc)) |pwd| { - copy.@"working-directory" = pwd; - } - } - } - - return copy; + return config.shallowClone(app.alloc); } diff --git a/src/config.zig b/src/config.zig index ba87fb6db6..b2c55ed7ea 100644 --- a/src/config.zig +++ b/src/config.zig @@ -20,6 +20,7 @@ pub const RepeatableCodepointMap = Config.RepeatableCodepointMap; pub const RepeatableFontVariation = Config.RepeatableFontVariation; pub const RepeatableString = Config.RepeatableString; pub const ShellIntegrationFeatures = Config.ShellIntegrationFeatures; +pub const WindowInheritWorkingDirectory = Config.WindowInheritWorkingDirectory; // Alternate APIs pub const CAPI = @import("config/CAPI.zig"); diff --git a/src/config/Config.zig b/src/config/Config.zig index a9bbc226b3..f6a9cfcbc5 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -679,10 +679,21 @@ keybind: Keybinds = .{}, /// This setting is only supported currently on macOS. @"window-vsync": bool = true, -/// If true, new windows and tabs will inherit the working directory of the -/// previously focused window. If no window was previously focused, the default -/// working directory will be used (the `working-directory` option). -@"window-inherit-working-directory": bool = true, +/// Controls whether new splits, tabs, and windows will inherit the working +/// directory of the previously focused window. If no window was previously +/// focused, the default working directory will be used (the `working-directory` +/// option). +/// +/// The format is a list of values to enable, separated by commas. If you +/// prefix a value with `no-` then it is disabled. If you omit a feature, +/// its default value is used, so you must explicitly disable features you +/// don't want. You can also use `true` or `false` to turn all values on or +/// off. +/// +/// Valid values are `split`, `tab`, and `window`. +/// +/// `split` and `tab` are currently only supported on macOS. +@"window-inherit-working-directory": WindowInheritWorkingDirectory = .{}, /// If true, new windows and tabs will inherit the font size of the previously /// focused window. If no window was previously focused, the default font size @@ -3740,6 +3751,13 @@ pub const WindowNewTabPosition = enum { end, }; +/// See window-inherit-working-directory +pub const WindowInheritWorkingDirectory = packed struct { + split: bool = true, + tab: bool = true, + window: bool = true, +}; + /// See grapheme-width-method pub const GraphemeWidthMethod = enum { legacy,