mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
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).
This commit is contained in:
@ -31,6 +31,12 @@ typedef void* ghostty_surface_t;
|
|||||||
typedef void* ghostty_inspector_t;
|
typedef void* ghostty_inspector_t;
|
||||||
|
|
||||||
// Enums are up top so we can reference them later.
|
// 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 {
|
typedef enum {
|
||||||
GHOSTTY_PLATFORM_INVALID,
|
GHOSTTY_PLATFORM_INVALID,
|
||||||
GHOSTTY_PLATFORM_MACOS,
|
GHOSTTY_PLATFORM_MACOS,
|
||||||
@ -395,6 +401,7 @@ typedef union {
|
|||||||
} ghostty_platform_u;
|
} ghostty_platform_u;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
|
ghostty_surface_kind_e kind;
|
||||||
ghostty_platform_e platform_tag;
|
ghostty_platform_e platform_tag;
|
||||||
ghostty_platform_u platform;
|
ghostty_platform_u platform;
|
||||||
void* userdata;
|
void* userdata;
|
||||||
|
@ -291,6 +291,8 @@ extension Ghostty {
|
|||||||
/// The configuration for a surface. For any configuration not set, defaults will be chosen from
|
/// The configuration for a surface. For any configuration not set, defaults will be chosen from
|
||||||
/// libghostty, usually from the Ghostty configuration.
|
/// libghostty, usually from the Ghostty configuration.
|
||||||
struct SurfaceConfiguration {
|
struct SurfaceConfiguration {
|
||||||
|
var kind: ghostty_surface_kind_e? = nil;
|
||||||
|
|
||||||
/// Explicit font size to use in points
|
/// Explicit font size to use in points
|
||||||
var fontSize: Float32? = nil
|
var fontSize: Float32? = nil
|
||||||
|
|
||||||
@ -303,6 +305,7 @@ extension Ghostty {
|
|||||||
init() {}
|
init() {}
|
||||||
|
|
||||||
init(from config: ghostty_surface_config_s) {
|
init(from config: ghostty_surface_config_s) {
|
||||||
|
self.kind = config.kind;
|
||||||
self.fontSize = config.font_size
|
self.fontSize = config.font_size
|
||||||
self.workingDirectory = String.init(cString: config.working_directory, encoding: .utf8)
|
self.workingDirectory = String.init(cString: config.working_directory, encoding: .utf8)
|
||||||
self.command = String.init(cString: config.command, encoding: .utf8)
|
self.command = String.init(cString: config.command, encoding: .utf8)
|
||||||
@ -334,6 +337,7 @@ extension Ghostty {
|
|||||||
#error("unsupported target")
|
#error("unsupported target")
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
if let kind = kind { config.kind = kind }
|
||||||
if let fontSize = fontSize { config.font_size = fontSize }
|
if let fontSize = fontSize { config.font_size = fontSize }
|
||||||
if let workingDirectory = workingDirectory {
|
if let workingDirectory = workingDirectory {
|
||||||
config.working_directory = (workingDirectory as NSString).utf8String
|
config.working_directory = (workingDirectory as NSString).utf8String
|
||||||
|
@ -125,6 +125,13 @@ config: DerivedConfig,
|
|||||||
/// This is used to determine if we need to confirm, hold open, etc.
|
/// This is used to determine if we need to confirm, hold open, etc.
|
||||||
child_exited: bool = false,
|
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 effect of an input event. This can be used by callers to take
|
||||||
/// the appropriate action after an input event. For example, key
|
/// the appropriate action after an input event. For example, key
|
||||||
/// input can be forwarded to the OS for further processing if it
|
/// input can be forwarded to the OS for further processing if it
|
||||||
|
@ -308,6 +308,9 @@ pub const Surface = struct {
|
|||||||
|
|
||||||
/// Surface initialization options.
|
/// Surface initialization options.
|
||||||
pub const Options = extern struct {
|
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 platform that this surface is being initialized for and
|
||||||
/// the associated platform-specific configuration.
|
/// the associated platform-specific configuration.
|
||||||
platform_tag: c_int = 0,
|
platform_tag: c_int = 0,
|
||||||
@ -365,7 +368,7 @@ pub const Surface = struct {
|
|||||||
errdefer app.core_app.deleteSurface(self);
|
errdefer app.core_app.deleteSurface(self);
|
||||||
|
|
||||||
// Shallow copy the config so that we can modify it.
|
// 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();
|
defer config.deinit();
|
||||||
|
|
||||||
// If we have a working directory from the options then we set it.
|
// If we have a working directory from the options then we set it.
|
||||||
@ -473,7 +476,7 @@ pub const Surface = struct {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = self.newSurfaceOptions();
|
const options = self.newSurfaceOptions(.split);
|
||||||
func(self.userdata, direction, options);
|
func(self.userdata, direction, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1024,7 +1027,7 @@ pub const Surface = struct {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = self.newSurfaceOptions();
|
const options = self.newSurfaceOptions(.tab);
|
||||||
func(self.userdata, options);
|
func(self.userdata, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1034,7 +1037,7 @@ pub const Surface = struct {
|
|||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
|
||||||
const options = self.newSurfaceOptions();
|
const options = self.newSurfaceOptions(.window);
|
||||||
func(self.userdata, options);
|
func(self.userdata, options);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1065,13 +1068,14 @@ pub const Surface = struct {
|
|||||||
func(self.userdata, width, height);
|
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: {
|
const font_size: f32 = font_size: {
|
||||||
if (!self.app.config.@"window-inherit-font-size") break :font_size 0;
|
if (!self.app.config.@"window-inherit-font-size") break :font_size 0;
|
||||||
break :font_size self.core_surface.font_size.points;
|
break :font_size self.core_surface.font_size.points;
|
||||||
};
|
};
|
||||||
|
|
||||||
return .{
|
return .{
|
||||||
|
.kind = kind,
|
||||||
.font_size = font_size,
|
.font_size = font_size,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -448,7 +448,8 @@ pub const Surface = struct {
|
|||||||
errdefer app.app.deleteSurface(self);
|
errdefer app.app.deleteSurface(self);
|
||||||
|
|
||||||
// Get our new surface config
|
// 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();
|
defer config.deinit();
|
||||||
|
|
||||||
// Initialize our surface now that we have the stable pointer.
|
// Initialize our surface now that we have the stable pointer.
|
||||||
|
@ -543,7 +543,8 @@ fn realize(self: *Surface) !void {
|
|||||||
errdefer self.app.core_app.deleteSurface(self);
|
errdefer self.app.core_app.deleteSurface(self);
|
||||||
|
|
||||||
// Get our new surface config
|
// 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();
|
defer config.deinit();
|
||||||
if (!self.parent_surface) {
|
if (!self.parent_surface) {
|
||||||
// A hack, see the "parent_surface" field for more information.
|
// A hack, see the "parent_surface" field for more information.
|
||||||
|
@ -1,3 +1,4 @@
|
|||||||
|
const std = @import("std");
|
||||||
const apprt = @import("../apprt.zig");
|
const apprt = @import("../apprt.zig");
|
||||||
const App = @import("../App.zig");
|
const App = @import("../App.zig");
|
||||||
const Surface = @import("../Surface.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
|
/// 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
|
/// used for any new surfaces. The resulting config should be deinitialized
|
||||||
/// after the surface is initialized.
|
/// 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
|
// Create a shallow clone
|
||||||
var copy = config.shallowClone(app.alloc);
|
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.
|
// Get our previously focused surface for some inherited values.
|
||||||
const prev = app.focusedSurface();
|
const prev = app.focusedSurface();
|
||||||
if (prev) |p| {
|
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| {
|
if (try p.pwd(alloc)) |pwd| {
|
||||||
copy.@"working-directory" = pwd;
|
copy.@"working-directory" = pwd;
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ pub const RepeatableCodepointMap = Config.RepeatableCodepointMap;
|
|||||||
pub const RepeatableFontVariation = Config.RepeatableFontVariation;
|
pub const RepeatableFontVariation = Config.RepeatableFontVariation;
|
||||||
pub const RepeatableString = Config.RepeatableString;
|
pub const RepeatableString = Config.RepeatableString;
|
||||||
pub const ShellIntegrationFeatures = Config.ShellIntegrationFeatures;
|
pub const ShellIntegrationFeatures = Config.ShellIntegrationFeatures;
|
||||||
|
pub const WindowInheritWorkingDirectory = Config.WindowInheritWorkingDirectory;
|
||||||
|
|
||||||
// Alternate APIs
|
// Alternate APIs
|
||||||
pub const CAPI = @import("config/CAPI.zig");
|
pub const CAPI = @import("config/CAPI.zig");
|
||||||
|
@ -33,6 +33,9 @@ const help_strings = @import("help_strings");
|
|||||||
|
|
||||||
const log = std.log.scoped(.config);
|
const log = std.log.scoped(.config);
|
||||||
|
|
||||||
|
// For trimming
|
||||||
|
const whitespace = " \t";
|
||||||
|
|
||||||
/// Used on Unixes for some defaults.
|
/// Used on Unixes for some defaults.
|
||||||
const c = @cImport({
|
const c = @cImport({
|
||||||
@cInclude("unistd.h");
|
@cInclude("unistd.h");
|
||||||
@ -679,10 +682,15 @@ keybind: Keybinds = .{},
|
|||||||
/// This setting is only supported currently on macOS.
|
/// This setting is only supported currently on macOS.
|
||||||
@"window-vsync": bool = true,
|
@"window-vsync": bool = true,
|
||||||
|
|
||||||
/// If true, new windows and tabs will inherit the working directory of the
|
/// Controls whether new splits, tabs, and windows will inherit the working
|
||||||
/// previously focused window. If no window was previously focused, the default
|
/// directory of the previously focused window. If no window was previously
|
||||||
/// working directory will be used (the `working-directory` option).
|
/// focused, the default working directory will be used (the `working-directory`
|
||||||
@"window-inherit-working-directory": bool = true,
|
/// 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
|
/// 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
|
/// 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 {
|
pub fn parseCLI(self: *Self, alloc: Allocator, input_: ?[]const u8) !void {
|
||||||
const input = input_ orelse return error.ValueRequired;
|
const input = input_ orelse return error.ValueRequired;
|
||||||
const eql_idx = std.mem.indexOf(u8, input, "=") orelse return error.InvalidValue;
|
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 key = std.mem.trim(u8, input[0..eql_idx], whitespace);
|
||||||
const value = std.mem.trim(u8, input[eql_idx + 1 ..], whitespace);
|
const value = std.mem.trim(u8, input[eql_idx + 1 ..], whitespace);
|
||||||
if (key.len != 4) return error.InvalidValue;
|
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 {
|
pub fn parseCLI(self: *Self, alloc: Allocator, input_: ?[]const u8) !void {
|
||||||
const input = input_ orelse return error.ValueRequired;
|
const input = input_ orelse return error.ValueRequired;
|
||||||
const eql_idx = std.mem.indexOf(u8, input, "=") orelse return error.InvalidValue;
|
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 key = std.mem.trim(u8, input[0..eql_idx], whitespace);
|
||||||
const value = std.mem.trim(u8, input[eql_idx + 1 ..], whitespace);
|
const value = std.mem.trim(u8, input[eql_idx + 1 ..], whitespace);
|
||||||
const valueZ = try alloc.dupeZ(u8, value);
|
const valueZ = try alloc.dupeZ(u8, value);
|
||||||
@ -3740,6 +3746,70 @@ pub const WindowNewTabPosition = enum {
|
|||||||
end,
|
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
|
/// See grapheme-width-method
|
||||||
pub const GraphemeWidthMethod = enum {
|
pub const GraphemeWidthMethod = enum {
|
||||||
legacy,
|
legacy,
|
||||||
|
Reference in New Issue
Block a user