Skip to content

Commit

Permalink
config: window-inherit-working-directory options
Browse files Browse the repository at this point in the history
`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).
  • Loading branch information
jparise committed Apr 17, 2024
1 parent 06c5528 commit 00bd212
Show file tree
Hide file tree
Showing 9 changed files with 116 additions and 15 deletions.
7 changes: 7 additions & 0 deletions include/ghostty.h
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,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,
Expand Down Expand Up @@ -375,6 +381,7 @@ typedef union {
} ghostty_platform_u;

typedef struct {
ghostty_surface_kind_e kind;
ghostty_platform_e platform_tag;
ghostty_platform_u platform;
void* userdata;
Expand Down
4 changes: 4 additions & 0 deletions macos/Sources/Ghostty/SurfaceView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -251,6 +251,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: UInt8? = nil

Expand All @@ -263,6 +265,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)
Expand Down Expand Up @@ -294,6 +297,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
Expand Down
7 changes: 7 additions & 0 deletions src/Surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
14 changes: 9 additions & 5 deletions src/apprt/embedded.zig
Original file line number Diff line number Diff line change
Expand Up @@ -301,6 +301,9 @@ pub const Surface = struct {
inspector: ?*Inspector = null,

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,
Expand Down Expand Up @@ -358,7 +361,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.
Expand Down Expand Up @@ -466,7 +469,7 @@ pub const Surface = struct {
return;
};

const options = self.newSurfaceOptions();
const options = self.newSurfaceOptions(.split);
func(self.opts.userdata, direction, options);
}

Expand Down Expand Up @@ -999,7 +1002,7 @@ pub const Surface = struct {
return;
};

const options = self.newSurfaceOptions();
const options = self.newSurfaceOptions(.tab);
func(self.opts.userdata, options);
}

Expand All @@ -1009,7 +1012,7 @@ pub const Surface = struct {
return;
};

const options = self.newSurfaceOptions();
const options = self.newSurfaceOptions(.window);
func(self.opts.userdata, options);
}

Expand Down Expand Up @@ -1040,13 +1043,14 @@ pub const Surface = struct {
func(self.opts.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: u8 = 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,
};
}
Expand Down
3 changes: 2 additions & 1 deletion src/apprt/glfw.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
3 changes: 2 additions & 1 deletion src/apprt/gtk/Surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -397,7 +397,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.
Expand Down
10 changes: 8 additions & 2 deletions src/apprt/surface.zig
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const std = @import("std");
const apprt = @import("../apprt.zig");
const App = @import("../App.zig");
const Surface = @import("../Surface.zig");
Expand Down Expand Up @@ -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);

Expand All @@ -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;
}
Expand Down
1 change: 1 addition & 0 deletions src/config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down
82 changes: 76 additions & 6 deletions src/config/Config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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");
Expand Down Expand Up @@ -613,10 +616,15 @@ keybind: Keybinds = .{},
/// given a certain viewport size and grid cell size.
@"window-padding-balance": bool = false,

/// 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
Expand Down Expand Up @@ -2687,7 +2695,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;
Expand Down Expand Up @@ -2950,7 +2957,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);
Expand Down Expand Up @@ -3462,6 +3468,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 {
wcswidth,
Expand Down

0 comments on commit 00bd212

Please sign in to comment.