Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

(alt) feat: customize quick terminal size #4716

Draft
wants to merge 1 commit into
base: main
Choose a base branch
from
Draft
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
175 changes: 175 additions & 0 deletions src/config/Config.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1561,6 +1561,40 @@ keybind: Keybinds = .{},
/// Set it to false for the quick terminal to remain open even when it loses focus.
@"quick-terminal-autohide": bool = true,

/// Sets the size of the "quick" terminal window is (re)created upon first use
/// of `toggle_quick_terminal` action—after Ghostty was restarted or the
/// previous window was explicitly closed (by `close_window` action or
/// termination of `command` when `wait-after-command` is `false`; not as
/// dismissed by loss of focus with `quick-terminal-autohide` set to `true`).
///
/// Either or both of `quick-terminal-height`/`quick-terminal-width` may be set
/// to one of the keyword values or sepcified numerically with supported units:
/// * `default` will use default size for the `quick-terminal-position` set:
/// + for `top` & `bottom` - height is as `25%` and width is as `full`
/// + for `left` & `right` - width is as `25%` and height is as `full`
/// + for `center` - width is as `50%` and height is as `33%`
/// * `full` will cause the window to always cover the full span of the screen
/// it cannot be manually resized to a smaller portion. If you want full-but-
/// resizeabile set to `100%` instead. For height of `full` this will
/// * `1%`..`100%` - what portion of the screen's height/width to cover.
/// * e.g. `400px` - the number of apparent resolution pixels. May not
/// correspond to actual hardware pixels when using "Retina"/HiDPI modes.
///
/// Numerical values exact meanings are dependent on `quick-terminal-position`:
/// * height for `top`/`bottom` & width for `left`/`right` specify distance
/// away from the "pinned" edge named by position towards screen center.
/// * "cross-axis" with width for `top`/`bottom` & height for `left`/`right`,
/// space not utilized by a given value will be evenly distributed to center
/// the window "along" the position-naming edge
/// * for `center` that distribution-of-excess happens along both axes, so you
/// might think of the a height or width value as giving the total (up+down
/// or left+right) distance from the screen center to be occupied.
///
/// If a given size exceeds that of display, it will be reduced for window to
/// fit. If height size is smaller than fits one line of text it will be grown.
@"quick-terminal-height": QuickTerminalDimension = .{ .default = {} },
@"quick-terminal-width": QuickTerminalDimension = .{ .default = {} },

/// Whether to enable shell integration auto-injection or not. Shell integration
/// greatly enhances the terminal experience by enabling a number of features:
///
Expand Down Expand Up @@ -5677,6 +5711,147 @@ pub const QuickTerminalScreen = enum {
@"macos-menu-bar",
};

/// See quick-terminal-width and quick-terminal-height
pub const QuickTerminalDimension = union(enum) {
const Self = @This();

default: void,
full: void,
percent: u8,
pixels: u16,

// pub const C = extern struct {
// tag: ValueTag,
// value: c_uint,
// };

// pub fn cval(self: Self) Self.C {
// return .{ .tag = @as(c_int, self.ValueTag), .value = @as(c_uint, switch (self.Value) {
// .pixels => |value| value,
// .percent => |value| value,
// .full => 0,
// }) };
// }

pub fn clone(self: Self, _: Allocator) error{}!Self {
return self;
}

pub fn equal(self: Self, other: Self) bool {
return std.meta.eql(self, other);
}

pub fn formatEntry(self: Self, formatter: anytype) !void {
var buf: [7]u8 = undefined;

switch (self) {
.default, .full => try formatter.formatEntry(
[]const u8,
@tagName(self),
),

.percent => |pct| try formatter.formatEntry(
[]const u8,
std.fmt.bufPrint(&buf, "{d}%", .{pct}) catch return error.OutOfMemory,
),
.pixels => |px| try formatter.formatEntry([]const u8, std.fmt.bufPrint(&buf, "{d}px", .{px}) catch return error.OutOfMemory),
}
}

pub fn parseCLI(self: *Self, input: ?[]const u8) !void {
const value = input orelse return error.ValueRequired;
if (value.len == 0) {
return error.ValueRequired;
}

if (std.mem.eql(u8, value, "default")) {
self.* = .{ .default = {} };
} else if (std.mem.eql(u8, value, "full")) {
self.* = .{ .full = {} };
} else if (value[value.len - 1] == '%') {
const v = std.fmt.parseInt(
u8,
value[0 .. value.len - 1],
10,
) catch return error.InvalidFormat;

// Percentage value must be between 0-100.
if (v > 100) return error.InvalidFormat;

self.* = .{ .percent = v };
} else if (value[value.len - 2] == 'p' and value[value.len - 1] == 'x') {
const v = std.fmt.parseInt(
u16,
value[0 .. value.len - 2],
10,
) catch return error.InvalidFormat;

self.* = .{ .pixels = v };
} else {
return error.InvalidFormat;
}
}

test "parseCLI" {
var p: Self = .{ .full = {} };

{
try p.parseCLI("default");
try std.testing.expectEqual(Self{ .default = {} }, p);
}
{
try p.parseCLI("full");
try std.testing.expectEqual(Self{ .full = {} }, p);
}
{
try p.parseCLI("42px");
try std.testing.expectEqual(Self{ .pixels = 42 }, p);
}
{
try p.parseCLI("25%");
try std.testing.expectEqual(Self{ .percent = 25 }, p);
}

try std.testing.expectError(error.InvalidFormat, p.parseCLI(""));
try std.testing.expectError(error.InvalidFormat, p.parseCLI("29"));
try std.testing.expectError(error.InvalidFormat, p.parseCLI("120%"));
try std.testing.expectError(error.InvalidFormat, p.parseCLI("65px,12"));
}

test "formatEntry pixels" {
const testing = std.testing;

var buf = std.ArrayList(u8).init(testing.allocator);
defer buf.deinit();

var p: Self = .{ .pixels = 1024 };
try p.formatEntry(formatterpkg.entryFormatter("quick-terminal-width", buf.writer()));
try testing.expectEqualSlices(u8, "quick-terminal-width = 1024px\n", buf.items);
}

test "formatEntry percent" {
const testing = std.testing;

var buf = std.ArrayList(u8).init(testing.allocator);
defer buf.deinit();

var p: Self = .{ .percent = 60 };
try p.formatEntry(formatterpkg.entryFormatter("quick-terminal-width", buf.writer()));
try testing.expectEqualSlices(u8, "quick-terminal-width = 60%\n", buf.items);
}

test "formatEntry full" {
const testing = std.testing;

var buf = std.ArrayList(u8).init(testing.allocator);
defer buf.deinit();

var p: Self = .{ .full = {} };
try p.formatEntry(formatterpkg.entryFormatter("quick-terminal-width", buf.writer()));
try testing.expectEqualSlices(u8, "quick-terminal-width = full\n", buf.items);
}
};

/// See grapheme-width-method
pub const GraphemeWidthMethod = enum {
legacy,
Expand Down