From f5ddb36c944907ca8f15f6fd731662ac73823583 Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Wed, 17 Apr 2024 16:24:09 -0700 Subject: [PATCH 1/4] config: window-inherit-working-directory options `window-inherit-working-directory` was previously a boolean value. This change makes it a more granular option, allowing you to enable this behavior for one or more surface "kinds": split, tab, window. The configuration value can be `true` (all), `false` (none), or a comma-delimited string of kinds (e.g. `tab` or `split,window`). The full behavior is only implemented for the embedded runtime and macOS app. The other runtimes still need their own plumbing to track surface kind creation (stubbed as `.window` by default in this change). --- include/ghostty.h | 7 +++ macos/Sources/Ghostty/SurfaceView.swift | 4 ++ src/Surface.zig | 7 +++ src/apprt/embedded.zig | 14 +++-- src/apprt/glfw.zig | 3 +- src/apprt/gtk/Surface.zig | 3 +- src/apprt/surface.zig | 10 ++- src/config.zig | 1 + src/config/Config.zig | 82 +++++++++++++++++++++++-- 9 files changed, 116 insertions(+), 15 deletions(-) diff --git a/include/ghostty.h b/include/ghostty.h index 2feb35ad99..f33428fb65 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -31,6 +31,12 @@ typedef void* ghostty_surface_t; typedef void* ghostty_inspector_t; // Enums are up top so we can reference them later. +typedef enum { + GHOSTTY_SURFACE_KIND_SPLIT, + GHOSTTY_SURFACE_KIND_TAB, + GHOSTTY_SURFACE_KIND_WINDOW, +} ghostty_surface_kind_e; + typedef enum { GHOSTTY_PLATFORM_INVALID, GHOSTTY_PLATFORM_MACOS, @@ -395,6 +401,7 @@ typedef union { } ghostty_platform_u; typedef struct { + ghostty_surface_kind_e kind; ghostty_platform_e platform_tag; ghostty_platform_u platform; void* userdata; diff --git a/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index 92d8993004..8172b01b6f 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -291,6 +291,8 @@ extension Ghostty { /// The configuration for a surface. For any configuration not set, defaults will be chosen from /// libghostty, usually from the Ghostty configuration. struct SurfaceConfiguration { + var kind: ghostty_surface_kind_e? = nil; + /// Explicit font size to use in points var fontSize: Float32? = nil @@ -303,6 +305,7 @@ extension Ghostty { init() {} init(from config: ghostty_surface_config_s) { + self.kind = config.kind; self.fontSize = config.font_size self.workingDirectory = String.init(cString: config.working_directory, encoding: .utf8) self.command = String.init(cString: config.command, encoding: .utf8) @@ -334,6 +337,7 @@ extension Ghostty { #error("unsupported target") #endif + if let kind = kind { config.kind = kind } if let fontSize = fontSize { config.font_size = fontSize } if let workingDirectory = workingDirectory { config.working_directory = (workingDirectory as NSString).utf8String diff --git a/src/Surface.zig b/src/Surface.zig index bb351a9c48..d8eb0865a8 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(c_int) { + 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..406fa0a115 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -308,6 +308,9 @@ pub const Surface = struct { /// Surface initialization options. pub const Options = extern struct { + /// The initial kind of surface. + kind: CoreSurface.Kind = .window, + /// The platform that this surface is being initialized for and /// the associated platform-specific configuration. platform_tag: c_int = 0, @@ -365,7 +368,7 @@ pub const Surface = struct { errdefer app.core_app.deleteSurface(self); // Shallow copy the config so that we can modify it. - var config = try apprt.surface.newConfig(app.core_app, app.config); + var config = try apprt.surface.newConfig(app.core_app, app.config, opts.kind); defer config.deinit(); // If we have a working directory from the options then we set it. @@ -473,7 +476,7 @@ pub const Surface = struct { return; }; - const options = self.newSurfaceOptions(); + const options = self.newSurfaceOptions(.split); func(self.userdata, direction, options); } @@ -1024,7 +1027,7 @@ pub const Surface = struct { return; }; - const options = self.newSurfaceOptions(); + const options = self.newSurfaceOptions(.tab); func(self.userdata, options); } @@ -1034,7 +1037,7 @@ pub const Surface = struct { return; }; - const options = self.newSurfaceOptions(); + const options = self.newSurfaceOptions(.window); func(self.userdata, options); } @@ -1065,13 +1068,14 @@ 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; }; return .{ + .kind = kind, .font_size = font_size, }; } diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index 81063bc696..a50ce4f2a0 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -448,7 +448,8 @@ pub const Surface = struct { errdefer app.app.deleteSurface(self); // Get our new surface config - var config = try apprt.surface.newConfig(app.app, &app.config); + const kind: CoreSurface.Kind = .window; // TODO + var config = try apprt.surface.newConfig(app.app, &app.config, kind); defer config.deinit(); // Initialize our surface now that we have the stable pointer. diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index ee6c6869ea..29b6c9ab54 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -543,7 +543,8 @@ fn realize(self: *Surface) !void { errdefer self.app.core_app.deleteSurface(self); // Get our new surface config - var config = try apprt.surface.newConfig(self.app.core_app, &self.app.config); + const kind: CoreSurface.Kind = .window; // TODO + var config = try apprt.surface.newConfig(self.app.core_app, &self.app.config, kind); defer config.deinit(); if (!self.parent_surface) { // A hack, see the "parent_surface" field for more information. diff --git a/src/apprt/surface.zig b/src/apprt/surface.zig index ae3ba050ab..92ed85b68e 100644 --- a/src/apprt/surface.zig +++ b/src/apprt/surface.zig @@ -1,3 +1,4 @@ +const std = @import("std"); const apprt = @import("../apprt.zig"); const App = @import("../App.zig"); const Surface = @import("../Surface.zig"); @@ -85,7 +86,7 @@ pub const Mailbox = struct { /// Returns a new config for a surface for the given app that should be /// used for any new surfaces. The resulting config should be deinitialized /// after the surface is initialized. -pub fn newConfig(app: *const App, config: *const Config) !Config { +pub fn newConfig(app: *const App, config: *const Config, kind: Surface.Kind) !Config { // Create a shallow clone var copy = config.shallowClone(app.alloc); @@ -95,7 +96,12 @@ pub fn newConfig(app: *const App, config: *const Config) !Config { // Get our previously focused surface for some inherited values. const prev = app.focusedSurface(); if (prev) |p| { - if (config.@"window-inherit-working-directory") { + const inherit_pwd: bool = switch (kind) { + .split => config.@"window-inherit-working-directory".split, + .tab => config.@"window-inherit-working-directory".tab, + .window => config.@"window-inherit-working-directory".window, + }; + if (inherit_pwd) { if (try p.pwd(alloc)) |pwd| { copy.@"working-directory" = pwd; } 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..615b66e824 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -33,6 +33,9 @@ const help_strings = @import("help_strings"); const log = std.log.scoped(.config); +// For trimming +const whitespace = " \t"; + /// Used on Unixes for some defaults. const c = @cImport({ @cInclude("unistd.h"); @@ -679,10 +682,15 @@ 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). +/// +/// Value value are `split`, `tab`, and `window`. You can specify multiple +/// values using a comma-delimited string (`tab` or `split,window`). You +/// can also set this to `true` (always inherit) or `false` (never inherit). +@"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 @@ -2956,7 +2964,6 @@ pub const RepeatableFontVariation = struct { pub fn parseCLI(self: *Self, alloc: Allocator, input_: ?[]const u8) !void { const input = input_ orelse return error.ValueRequired; const eql_idx = std.mem.indexOf(u8, input, "=") orelse return error.InvalidValue; - const whitespace = " \t"; const key = std.mem.trim(u8, input[0..eql_idx], whitespace); const value = std.mem.trim(u8, input[eql_idx + 1 ..], whitespace); if (key.len != 4) return error.InvalidValue; @@ -3219,7 +3226,6 @@ pub const RepeatableCodepointMap = struct { pub fn parseCLI(self: *Self, alloc: Allocator, input_: ?[]const u8) !void { const input = input_ orelse return error.ValueRequired; const eql_idx = std.mem.indexOf(u8, input, "=") orelse return error.InvalidValue; - const whitespace = " \t"; const key = std.mem.trim(u8, input[0..eql_idx], whitespace); const value = std.mem.trim(u8, input[eql_idx + 1 ..], whitespace); const valueZ = try alloc.dupeZ(u8, value); @@ -3740,6 +3746,70 @@ pub const WindowNewTabPosition = enum { end, }; +/// Options for inheriting the working directory of the previous window. +pub const WindowInheritWorkingDirectory = packed struct { + const Self = @This(); + + split: bool = false, + tab: bool = false, + window: bool = false, + + pub fn parseCLI(self: *Self, _: Allocator, input: ?[]const u8) !void { + const value = input orelse return error.ValueRequired; + const fields = @typeInfo(Self).Struct.fields; + + if (std.mem.eql(u8, value, "true")) { + self.* = .{ .split = true, .tab = true, .window = true }; + return; + } + + if (std.mem.eql(u8, value, "false")) { + self.* = .{ .split = false, .tab = false, .window = false }; + return; + } + + // Enable all of the fields named in the comma-separated value. + self.* = .{}; + var iter = std.mem.splitSequence(u8, value, ","); + loop: while (iter.next()) |part_raw| { + const part = std.mem.trim(u8, part_raw, whitespace); + + inline for (fields) |field| { + assert(field.type == bool); + if (std.mem.eql(u8, field.name, part)) { + @field(self, field.name) = true; + continue :loop; + } + } + + // No field matched + return error.InvalidValue; + } + } + + test "parseCLI" { + const testing = std.testing; + var arena = ArenaAllocator.init(testing.allocator); + defer arena.deinit(); + const alloc = arena.allocator(); + + var p: Self = .{}; + try p.parseCLI(alloc, "true"); + try testing.expectEqual(Self{ .split = true, .tab = true, .window = true }, p); + + try p.parseCLI(alloc, "false"); + try testing.expectEqual(Self{ .split = false, .tab = false, .window = false }, p); + + try p.parseCLI(alloc, "tab"); + try testing.expectEqual(Self{ .split = false, .tab = true, .window = false }, p); + + try p.parseCLI(alloc, "split,window"); + try testing.expectEqual(Self{ .split = true, .tab = false, .window = true }, p); + + try testing.expectError(error.InvalidValue, p.parseCLI(alloc, "unknown")); + } +}; + /// See grapheme-width-method pub const GraphemeWidthMethod = enum { legacy, From 834fe8d7f6d2cbf63a98e119b847fae5e71ea381 Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Wed, 17 Apr 2024 21:52:11 -0700 Subject: [PATCH 2/4] config: restore window-inherit-working-directory defaults This setting previously defaulted to `true`, so flip all of our packed booleans on by default. Also, note that the granular values are only supported on macOS. --- src/config/Config.zig | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index 615b66e824..e1407d429b 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -687,9 +687,11 @@ keybind: Keybinds = .{}, /// focused, the default working directory will be used (the `working-directory` /// option). /// -/// Value value are `split`, `tab`, and `window`. You can specify multiple +/// Valid values are `split`, `tab`, and `window`. You can specify multiple /// values using a comma-delimited string (`tab` or `split,window`). You /// can also set this to `true` (always inherit) or `false` (never inherit). +/// +/// `split`, `tab`, and `window` 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 @@ -3750,9 +3752,9 @@ pub const WindowNewTabPosition = enum { pub const WindowInheritWorkingDirectory = packed struct { const Self = @This(); - split: bool = false, - tab: bool = false, - window: bool = false, + split: bool = true, + tab: bool = true, + window: bool = true, pub fn parseCLI(self: *Self, _: Allocator, input: ?[]const u8) !void { const value = input orelse return error.ValueRequired; @@ -3769,7 +3771,7 @@ pub const WindowInheritWorkingDirectory = packed struct { } // Enable all of the fields named in the comma-separated value. - self.* = .{}; + self.* = .{ .split = false, .tab = false, .window = false }; var iter = std.mem.splitSequence(u8, value, ","); loop: while (iter.next()) |part_raw| { const part = std.mem.trim(u8, part_raw, whitespace); From ec2f8e66262890e7f31a55d99218150c46b8a117 Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Thu, 30 May 2024 10:27:55 -0400 Subject: [PATCH 3/4] config: use the standard 'packed struct' behavior --- src/config/Config.zig | 76 +++++++------------------------------------ 1 file changed, 11 insertions(+), 65 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index e1407d429b..f6a9cfcbc5 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -33,9 +33,6 @@ const help_strings = @import("help_strings"); const log = std.log.scoped(.config); -// For trimming -const whitespace = " \t"; - /// Used on Unixes for some defaults. const c = @cImport({ @cInclude("unistd.h"); @@ -687,11 +684,15 @@ keybind: Keybinds = .{}, /// focused, the default working directory will be used (the `working-directory` /// option). /// -/// Valid values are `split`, `tab`, and `window`. You can specify multiple -/// values using a comma-delimited string (`tab` or `split,window`). You -/// can also set this to `true` (always inherit) or `false` (never inherit). +/// 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`, `tab`, and `window` are currently only supported on macOS. +/// `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 @@ -2966,6 +2967,7 @@ pub const RepeatableFontVariation = struct { pub fn parseCLI(self: *Self, alloc: Allocator, input_: ?[]const u8) !void { const input = input_ orelse return error.ValueRequired; const eql_idx = std.mem.indexOf(u8, input, "=") orelse return error.InvalidValue; + const whitespace = " \t"; const key = std.mem.trim(u8, input[0..eql_idx], whitespace); const value = std.mem.trim(u8, input[eql_idx + 1 ..], whitespace); if (key.len != 4) return error.InvalidValue; @@ -3228,6 +3230,7 @@ pub const RepeatableCodepointMap = struct { pub fn parseCLI(self: *Self, alloc: Allocator, input_: ?[]const u8) !void { const input = input_ orelse return error.ValueRequired; const eql_idx = std.mem.indexOf(u8, input, "=") orelse return error.InvalidValue; + const whitespace = " \t"; const key = std.mem.trim(u8, input[0..eql_idx], whitespace); const value = std.mem.trim(u8, input[eql_idx + 1 ..], whitespace); const valueZ = try alloc.dupeZ(u8, value); @@ -3748,68 +3751,11 @@ pub const WindowNewTabPosition = enum { end, }; -/// Options for inheriting the working directory of the previous window. +/// See window-inherit-working-directory pub const WindowInheritWorkingDirectory = packed struct { - const Self = @This(); - split: bool = true, tab: bool = true, window: bool = true, - - pub fn parseCLI(self: *Self, _: Allocator, input: ?[]const u8) !void { - const value = input orelse return error.ValueRequired; - const fields = @typeInfo(Self).Struct.fields; - - if (std.mem.eql(u8, value, "true")) { - self.* = .{ .split = true, .tab = true, .window = true }; - return; - } - - if (std.mem.eql(u8, value, "false")) { - self.* = .{ .split = false, .tab = false, .window = false }; - return; - } - - // Enable all of the fields named in the comma-separated value. - self.* = .{ .split = false, .tab = false, .window = false }; - var iter = std.mem.splitSequence(u8, value, ","); - loop: while (iter.next()) |part_raw| { - const part = std.mem.trim(u8, part_raw, whitespace); - - inline for (fields) |field| { - assert(field.type == bool); - if (std.mem.eql(u8, field.name, part)) { - @field(self, field.name) = true; - continue :loop; - } - } - - // No field matched - return error.InvalidValue; - } - } - - test "parseCLI" { - const testing = std.testing; - var arena = ArenaAllocator.init(testing.allocator); - defer arena.deinit(); - const alloc = arena.allocator(); - - var p: Self = .{}; - try p.parseCLI(alloc, "true"); - try testing.expectEqual(Self{ .split = true, .tab = true, .window = true }, p); - - try p.parseCLI(alloc, "false"); - try testing.expectEqual(Self{ .split = false, .tab = false, .window = false }, p); - - try p.parseCLI(alloc, "tab"); - try testing.expectEqual(Self{ .split = false, .tab = true, .window = false }, p); - - try p.parseCLI(alloc, "split,window"); - try testing.expectEqual(Self{ .split = true, .tab = false, .window = true }, p); - - try testing.expectError(error.InvalidValue, p.parseCLI(alloc, "unknown")); - } }; /// See grapheme-width-method From 2fb84343a4b7055032ba2b3e1ad57b61e49a0aba Mon Sep 17 00:00:00 2001 From: Jon Parise Date: Thu, 25 Jul 2024 09:50:15 -0400 Subject: [PATCH 4/4] config: switch to a Surface.Options-based approach This introduces an ArenaAllocator that models the lifetime of the Surface.Options structure from initial surface creation command to the actual surface creation. This implementation is a little rough and relies on an `anyopaque` pointer (because `extern struct`), but it demonstrates a more apprt-centric approach. Still only implemented for embedded apprt and macOS. The other apprts will need their own plumbing to support this approach. We can also chose to clean up newConfig() and call Config.shallowClone() directly after this change. --- include/ghostty.h | 8 +--- macos/Sources/Ghostty/SurfaceView.swift | 4 -- src/Surface.zig | 2 +- src/apprt/embedded.zig | 57 +++++++++++++++++++++---- src/apprt/glfw.zig | 3 +- src/apprt/gtk/Surface.zig | 3 +- src/apprt/surface.zig | 25 +---------- 7 files changed, 54 insertions(+), 48 deletions(-) diff --git a/include/ghostty.h b/include/ghostty.h index f33428fb65..9d69f5d056 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -31,12 +31,6 @@ typedef void* ghostty_surface_t; typedef void* ghostty_inspector_t; // Enums are up top so we can reference them later. -typedef enum { - GHOSTTY_SURFACE_KIND_SPLIT, - GHOSTTY_SURFACE_KIND_TAB, - GHOSTTY_SURFACE_KIND_WINDOW, -} ghostty_surface_kind_e; - typedef enum { GHOSTTY_PLATFORM_INVALID, GHOSTTY_PLATFORM_MACOS, @@ -401,7 +395,6 @@ typedef union { } ghostty_platform_u; typedef struct { - ghostty_surface_kind_e kind; ghostty_platform_e platform_tag; ghostty_platform_u platform; void* userdata; @@ -409,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/macos/Sources/Ghostty/SurfaceView.swift b/macos/Sources/Ghostty/SurfaceView.swift index 8172b01b6f..92d8993004 100644 --- a/macos/Sources/Ghostty/SurfaceView.swift +++ b/macos/Sources/Ghostty/SurfaceView.swift @@ -291,8 +291,6 @@ extension Ghostty { /// The configuration for a surface. For any configuration not set, defaults will be chosen from /// libghostty, usually from the Ghostty configuration. struct SurfaceConfiguration { - var kind: ghostty_surface_kind_e? = nil; - /// Explicit font size to use in points var fontSize: Float32? = nil @@ -305,7 +303,6 @@ extension Ghostty { init() {} init(from config: ghostty_surface_config_s) { - self.kind = config.kind; self.fontSize = config.font_size self.workingDirectory = String.init(cString: config.working_directory, encoding: .utf8) self.command = String.init(cString: config.command, encoding: .utf8) @@ -337,7 +334,6 @@ extension Ghostty { #error("unsupported target") #endif - if let kind = kind { config.kind = kind } if let fontSize = fontSize { config.font_size = fontSize } if let workingDirectory = workingDirectory { config.working_directory = (workingDirectory as NSString).utf8String diff --git a/src/Surface.zig b/src/Surface.zig index d8eb0865a8..b1093de5f0 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -126,7 +126,7 @@ config: DerivedConfig, child_exited: bool = false, /// The kind of surface. -pub const Kind = enum(c_int) { +pub const Kind = enum { split, tab, window, diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index 406fa0a115..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"); @@ -308,9 +309,6 @@ pub const Surface = struct { /// Surface initialization options. pub const Options = extern struct { - /// The initial kind of surface. - kind: CoreSurface.Kind = .window, - /// The platform that this surface is being initialized for and /// the associated platform-specific configuration. platform_tag: c_int = 0, @@ -332,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. @@ -368,7 +377,7 @@ pub const Surface = struct { errdefer app.core_app.deleteSurface(self); // Shallow copy the config so that we can modify it. - var config = try apprt.surface.newConfig(app.core_app, app.config, opts.kind); + var config = try apprt.surface.newConfig(app.core_app, app.config); defer config.deinit(); // If we have a working directory from the options then we set it. @@ -476,7 +485,7 @@ pub const Surface = struct { return; }; - const options = self.newSurfaceOptions(.split); + const options = try self.newSurfaceOptions(.split); func(self.userdata, direction, options); } @@ -1027,7 +1036,7 @@ pub const Surface = struct { return; }; - const options = self.newSurfaceOptions(.tab); + const options = try self.newSurfaceOptions(.tab); func(self.userdata, options); } @@ -1037,7 +1046,7 @@ pub const Surface = struct { return; }; - const options = self.newSurfaceOptions(.window); + const options = try self.newSurfaceOptions(.window); func(self.userdata, options); } @@ -1068,15 +1077,44 @@ pub const Surface = struct { func(self.userdata, width, height); } - fn newSurfaceOptions(self: *const Surface, kind: CoreSurface.Kind) 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 .{ - .kind = kind, .font_size = font_size, + .working_directory = working_directory, + ._arena = arena, }; } @@ -1503,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/glfw.zig b/src/apprt/glfw.zig index a50ce4f2a0..81063bc696 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -448,8 +448,7 @@ pub const Surface = struct { errdefer app.app.deleteSurface(self); // Get our new surface config - const kind: CoreSurface.Kind = .window; // TODO - var config = try apprt.surface.newConfig(app.app, &app.config, kind); + var config = try apprt.surface.newConfig(app.app, &app.config); defer config.deinit(); // Initialize our surface now that we have the stable pointer. diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 29b6c9ab54..ee6c6869ea 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -543,8 +543,7 @@ fn realize(self: *Surface) !void { errdefer self.app.core_app.deleteSurface(self); // Get our new surface config - const kind: CoreSurface.Kind = .window; // TODO - var config = try apprt.surface.newConfig(self.app.core_app, &self.app.config, kind); + var config = try apprt.surface.newConfig(self.app.core_app, &self.app.config); defer config.deinit(); if (!self.parent_surface) { // A hack, see the "parent_surface" field for more information. diff --git a/src/apprt/surface.zig b/src/apprt/surface.zig index 92ed85b68e..384cce0a2f 100644 --- a/src/apprt/surface.zig +++ b/src/apprt/surface.zig @@ -1,4 +1,3 @@ -const std = @import("std"); const apprt = @import("../apprt.zig"); const App = @import("../App.zig"); const Surface = @import("../Surface.zig"); @@ -86,27 +85,7 @@ pub const Mailbox = struct { /// Returns a new config for a surface for the given app that should be /// used for any new surfaces. The resulting config should be deinitialized /// after the surface is initialized. -pub fn newConfig(app: *const App, config: *const Config, kind: Surface.Kind) !Config { +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| { - const inherit_pwd: bool = switch (kind) { - .split => config.@"window-inherit-working-directory".split, - .tab => config.@"window-inherit-working-directory".tab, - .window => config.@"window-inherit-working-directory".window, - }; - if (inherit_pwd) { - if (try p.pwd(alloc)) |pwd| { - copy.@"working-directory" = pwd; - } - } - } - - return copy; + return config.shallowClone(app.alloc); }