mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
feat: customize quick terminal size
This commit introduce `quick-terminal-size` option which allows to define the size of the quick terminal. It also fixes an issue where the quick terminal position was not properly updated when reloading the configuration. Resolves #2384
This commit is contained in:
@ -348,6 +348,22 @@ typedef struct {
|
||||
size_t len;
|
||||
} ghostty_config_color_list_s;
|
||||
|
||||
// config.QuickTerminalSize
|
||||
typedef enum {
|
||||
GHOSTTY_QUICK_TERMINAL_PIXEL_UNIT,
|
||||
GHOSTTY_QUICK_TERMINAL_PERCENTAGE_UNIT,
|
||||
} ghostty_config_quick_terminal_unit_e;
|
||||
|
||||
typedef struct {
|
||||
uint16_t value;
|
||||
ghostty_config_quick_terminal_unit_e unit;
|
||||
} ghostty_config_quick_terminal_dimension_s;
|
||||
|
||||
typedef struct {
|
||||
ghostty_config_quick_terminal_dimension_s* dimensions;
|
||||
size_t len;
|
||||
} ghostty_config_quick_terminal_size_s;
|
||||
|
||||
// apprt.Target.Key
|
||||
typedef enum {
|
||||
GHOSTTY_TARGET_APP,
|
||||
|
@ -92,6 +92,7 @@
|
||||
A5E112952AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E112942AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift */; };
|
||||
A5E112972AF7401B00C6E0C2 /* ClipboardConfirmationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5E112962AF7401B00C6E0C2 /* ClipboardConfirmationView.swift */; };
|
||||
A5FEB3002ABB69450068369E /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5FEB2FF2ABB69450068369E /* main.swift */; };
|
||||
AB315D402D1DCC6B0012D326 /* QuickTerminalSize.swift in Sources */ = {isa = PBXBuildFile; fileRef = AB315D3F2D1DCC630012D326 /* QuickTerminalSize.swift */; };
|
||||
AEE8B3452B9AA39600260C5E /* NSPasteboard+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */; };
|
||||
AEF9CE242B6AD07A0017E195 /* TerminalToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = AEF9CE232B6AD07A0017E195 /* TerminalToolbar.swift */; };
|
||||
C159E81D2B66A06B00FDFE9C /* OSColor+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */; };
|
||||
@ -183,6 +184,7 @@
|
||||
A5E112942AF73E8A00C6E0C2 /* ClipboardConfirmationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipboardConfirmationController.swift; sourceTree = "<group>"; };
|
||||
A5E112962AF7401B00C6E0C2 /* ClipboardConfirmationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ClipboardConfirmationView.swift; sourceTree = "<group>"; };
|
||||
A5FEB2FF2ABB69450068369E /* main.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = main.swift; sourceTree = "<group>"; };
|
||||
AB315D3F2D1DCC630012D326 /* QuickTerminalSize.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuickTerminalSize.swift; sourceTree = "<group>"; };
|
||||
AEE8B3442B9AA39600260C5E /* NSPasteboard+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSPasteboard+Extension.swift"; sourceTree = "<group>"; };
|
||||
AEF9CE232B6AD07A0017E195 /* TerminalToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TerminalToolbar.swift; sourceTree = "<group>"; };
|
||||
C159E81C2B66A06B00FDFE9C /* OSColor+Extension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OSColor+Extension.swift"; sourceTree = "<group>"; };
|
||||
@ -434,6 +436,7 @@
|
||||
A5CBD05A2CA0C5910017A1AE /* QuickTerminal */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
AB315D3F2D1DCC630012D326 /* QuickTerminalSize.swift */,
|
||||
A5CBD05B2CA0C5C70017A1AE /* QuickTerminal.xib */,
|
||||
A5CBD05D2CA0C5E70017A1AE /* QuickTerminalController.swift */,
|
||||
A5CBD0632CA122E70017A1AE /* QuickTerminalPosition.swift */,
|
||||
@ -633,6 +636,7 @@
|
||||
A5333E1C2B5A1CE3008AEFF7 /* CrossKit.swift in Sources */,
|
||||
A59444F729A2ED5200725BBA /* SettingsView.swift in Sources */,
|
||||
A56D58862ACDDB4100508D2C /* Ghostty.Shell.swift in Sources */,
|
||||
AB315D402D1DCC6B0012D326 /* QuickTerminalSize.swift in Sources */,
|
||||
A5985CD72C320C4500C57AD3 /* String+Extension.swift in Sources */,
|
||||
A59630A22AF0415000D64628 /* Ghostty.TerminalSplit.swift in Sources */,
|
||||
A5FEB3002ABB69450068369E /* main.swift in Sources */,
|
||||
|
@ -8,7 +8,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
override var windowNibName: NSNib.Name? { "QuickTerminal" }
|
||||
|
||||
/// The position for the quick terminal.
|
||||
let position: QuickTerminalPosition
|
||||
private var position: QuickTerminalPosition
|
||||
|
||||
/// The current state of the quick terminal
|
||||
private(set) var visible: Bool = false
|
||||
@ -72,7 +72,7 @@ class QuickTerminalController: BaseTerminalController {
|
||||
syncAppearance(ghostty.config)
|
||||
|
||||
// Setup our initial size based on our configured position
|
||||
position.setLoaded(window)
|
||||
derivedConfig.quickTerminalSize.apply(window, position)
|
||||
|
||||
// Setup our content
|
||||
window.contentView = NSHostingView(rootView: TerminalView(
|
||||
@ -306,6 +306,9 @@ class QuickTerminalController: BaseTerminalController {
|
||||
|
||||
private func syncAppearance(_ config: Ghostty.Config) {
|
||||
guard let window else { return }
|
||||
|
||||
// Update the quick terminal size right away
|
||||
config.quickTerminalSize.apply(window, config.quickTerminalPosition)
|
||||
|
||||
// If our window is not visible, then delay this. This is possible specifically
|
||||
// during state restoration but probably in other scenarios as well. To delay,
|
||||
@ -390,7 +393,8 @@ class QuickTerminalController: BaseTerminalController {
|
||||
|
||||
// Update our derived config
|
||||
self.derivedConfig = DerivedConfig(config)
|
||||
|
||||
self.position = self.derivedConfig.quickTerminalPosition
|
||||
|
||||
syncAppearance(config)
|
||||
}
|
||||
|
||||
@ -398,17 +402,23 @@ class QuickTerminalController: BaseTerminalController {
|
||||
let quickTerminalScreen: QuickTerminalScreen
|
||||
let quickTerminalAnimationDuration: Double
|
||||
let quickTerminalAutoHide: Bool
|
||||
let quickTerminalPosition: QuickTerminalPosition
|
||||
let quickTerminalSize: QuickTerminalSize
|
||||
|
||||
init() {
|
||||
self.quickTerminalScreen = .main
|
||||
self.quickTerminalAnimationDuration = 0.2
|
||||
self.quickTerminalAutoHide = true
|
||||
self.quickTerminalPosition = .top
|
||||
self.quickTerminalSize = .init()
|
||||
}
|
||||
|
||||
init(_ config: Ghostty.Config) {
|
||||
self.quickTerminalScreen = config.quickTerminalScreen
|
||||
self.quickTerminalAnimationDuration = config.quickTerminalAnimationDuration
|
||||
self.quickTerminalAutoHide = config.quickTerminalAutoHide
|
||||
self.quickTerminalPosition = config.quickTerminalPosition
|
||||
self.quickTerminalSize = config.quickTerminalSize
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -7,36 +7,6 @@ enum QuickTerminalPosition : String {
|
||||
case right
|
||||
case center
|
||||
|
||||
/// Set the loaded state for a window.
|
||||
func setLoaded(_ window: NSWindow) {
|
||||
guard let screen = window.screen ?? NSScreen.main else { return }
|
||||
switch (self) {
|
||||
case .top, .bottom:
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: .init(
|
||||
width: screen.frame.width,
|
||||
height: screen.frame.height / 4)
|
||||
), display: false)
|
||||
|
||||
case .left, .right:
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: .init(
|
||||
width: screen.frame.width / 4,
|
||||
height: screen.frame.height)
|
||||
), display: false)
|
||||
|
||||
case .center:
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: .init(
|
||||
width: screen.frame.width / 2,
|
||||
height: screen.frame.height / 3)
|
||||
), display: false)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the initial state for a window for animating out of this position.
|
||||
func setInitial(in window: NSWindow, on screen: NSScreen) {
|
||||
// We always start invisible
|
||||
@ -67,13 +37,12 @@ enum QuickTerminalPosition : String {
|
||||
switch (self) {
|
||||
case .top, .bottom:
|
||||
finalSize.width = screen.frame.width
|
||||
|
||||
|
||||
case .left, .right:
|
||||
finalSize.height = screen.frame.height
|
||||
|
||||
|
||||
case .center:
|
||||
finalSize.width = screen.frame.width / 2
|
||||
finalSize.height = screen.frame.height / 3
|
||||
break
|
||||
}
|
||||
|
||||
return finalSize
|
||||
|
@ -12,10 +12,10 @@ enum QuickTerminalScreen {
|
||||
|
||||
case "mouse":
|
||||
self = .mouse
|
||||
|
||||
|
||||
case "macos-menu-bar":
|
||||
self = .menuBar
|
||||
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
|
81
macos/Sources/Features/QuickTerminal/QuickTerminalSize.swift
Normal file
81
macos/Sources/Features/QuickTerminal/QuickTerminalSize.swift
Normal file
@ -0,0 +1,81 @@
|
||||
import Cocoa
|
||||
import GhosttyKit
|
||||
|
||||
class QuickTerminalSize {
|
||||
enum Size {
|
||||
case percent(value: Double)
|
||||
case pixel(value: UInt)
|
||||
|
||||
init?(c_dimension: ghostty_config_quick_terminal_dimension_s) {
|
||||
switch(c_dimension.unit) {
|
||||
case GHOSTTY_QUICK_TERMINAL_PIXEL_UNIT:
|
||||
self = .pixel(value: UInt(c_dimension.value))
|
||||
case GHOSTTY_QUICK_TERMINAL_PERCENTAGE_UNIT:
|
||||
self = .percent(value: Double(c_dimension.value) / 100.0)
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func apply(value: CGFloat) -> CGFloat {
|
||||
switch(self) {
|
||||
case .pixel(let fixed_size):
|
||||
return CGFloat(fixed_size);
|
||||
case .percent(let pct):
|
||||
return value * pct;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var mainDimension: Size;
|
||||
var secondDimension: Size;
|
||||
|
||||
init() {
|
||||
self.mainDimension = Size.percent(value: 0.25)
|
||||
self.secondDimension = Size.percent(value: 0.25)
|
||||
}
|
||||
|
||||
init(config: ghostty_config_quick_terminal_size_s) {
|
||||
switch (config.len) {
|
||||
case 1:
|
||||
self.mainDimension = Size(c_dimension: config.dimensions[0]) ?? Size.percent(value: 0.25)
|
||||
self.secondDimension = Size.percent(value: 0.25)
|
||||
case 2:
|
||||
self.mainDimension = Size(c_dimension: config.dimensions[0]) ?? Size.percent(value: 0.25)
|
||||
self.secondDimension = Size(c_dimension: config.dimensions[1]) ?? Size.percent(value: 0.25)
|
||||
default:
|
||||
self.mainDimension = Size.percent(value: 0.25)
|
||||
self.secondDimension = Size.percent(value: 0.25)
|
||||
}
|
||||
}
|
||||
|
||||
/// Set the window size.
|
||||
func apply(_ window: NSWindow, _ position: QuickTerminalPosition) {
|
||||
guard let screen = window.screen ?? NSScreen.main else { return }
|
||||
switch (position) {
|
||||
case .top, .bottom:
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: .init(
|
||||
width: screen.frame.width,
|
||||
height: self.mainDimension.apply(value: screen.frame.height))
|
||||
), display: false)
|
||||
|
||||
case .left, .right:
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: .init(
|
||||
width: self.mainDimension.apply(value: screen.frame.width),
|
||||
height: screen.frame.height)
|
||||
), display: false)
|
||||
|
||||
case .center:
|
||||
window.setFrame(.init(
|
||||
origin: window.frame.origin,
|
||||
size: .init(
|
||||
width: self.mainDimension.apply(value: screen.frame.width),
|
||||
height: self.secondDimension.apply(value: screen.frame.height))
|
||||
), display: false)
|
||||
}
|
||||
}
|
||||
}
|
@ -406,6 +406,14 @@ extension Ghostty {
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
return v
|
||||
}
|
||||
|
||||
var quickTerminalSize: QuickTerminalSize {
|
||||
guard let config = self.config else { return .init() }
|
||||
var v: ghostty_config_quick_terminal_size_s = .init()
|
||||
let key = "quick-terminal-size"
|
||||
_ = ghostty_config_get(config, &v, key, UInt(key.count))
|
||||
return .init(config: v);
|
||||
}
|
||||
#endif
|
||||
|
||||
var resizeOverlay: ResizeOverlay {
|
||||
|
@ -1411,6 +1411,31 @@ keybind: Keybinds = .{},
|
||||
/// Set it to false for the quick terminal to remain open even when it loses focus.
|
||||
@"quick-terminal-autohide": bool = true,
|
||||
|
||||
/// Control the size of the quick terminal.
|
||||
///
|
||||
/// The size can expressed in two units:
|
||||
/// * A value ending in `%` specifies a percentage of the screen size.
|
||||
/// * A value ending in `px` specifies a fixed size in pixel, which is clamped by the
|
||||
/// screen's maximum width or height.
|
||||
///
|
||||
/// The configuration accept one or two dimensions:
|
||||
/// * A single value specifies the dimension that is growable based on the quick terminal
|
||||
/// position:
|
||||
/// * For `top` and `bottom` positions, the value applies to the height.
|
||||
/// * For `right` and `left` positions, the value applies to the width.
|
||||
/// * For `center`, the size applied to both the width and height.
|
||||
/// * Two comma separated value specifies the width and height.
|
||||
///
|
||||
/// Examples:
|
||||
///
|
||||
/// ```
|
||||
/// quick-terminal-size = 25% // 25% of the maximum size for the growable dimension
|
||||
/// quick-terminal-size = 42px // 42 pixels for the growable dimension
|
||||
/// quick-terminal-size = 25%,75% // 25% for the primary dimension, 75% for the secondary
|
||||
/// quick-terminal-size = 300px,80% // 300px for the primary dimension, 80% for the secondary
|
||||
/// ```
|
||||
@"quick-terminal-size": ?QuickTerminalSize = null,
|
||||
|
||||
/// Whether to enable shell integration auto-injection or not. Shell integration
|
||||
/// greatly enhances the terminal experience by enabling a number of features:
|
||||
///
|
||||
@ -5294,6 +5319,240 @@ pub const QuickTerminalScreen = enum {
|
||||
@"macos-menu-bar",
|
||||
};
|
||||
|
||||
/// See quick-terminal-size
|
||||
pub const QuickTerminalSize = struct {
|
||||
const Self = @This();
|
||||
|
||||
pub const Size = union(UnitKey) {
|
||||
// Absolute value in pixel.
|
||||
pixel: u16,
|
||||
|
||||
// Percentage value relative to the screen size. Allowed value [0-100].
|
||||
percent: u8,
|
||||
|
||||
// Sync with `ghostty_config_quick_terminal_unit_e`.
|
||||
pub const UnitKey = enum(c_int) {
|
||||
pixel,
|
||||
percent,
|
||||
};
|
||||
|
||||
pub const C = extern struct {
|
||||
value: u16,
|
||||
tag: UnitKey,
|
||||
};
|
||||
|
||||
pub fn cval(self: Size) Size.C {
|
||||
return .{
|
||||
.tag = @as(UnitKey, self),
|
||||
.value = res: {
|
||||
switch (self) {
|
||||
.percent => |v| {
|
||||
break :res v;
|
||||
},
|
||||
.pixel => |v| {
|
||||
break :res @intCast(v);
|
||||
},
|
||||
}
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
fn parseValue(input: []const u8) !Size {
|
||||
if (input[input.len - 1] == '%') {
|
||||
const v = std.fmt.parseInt(
|
||||
u8,
|
||||
input[0 .. input.len - 1],
|
||||
10,
|
||||
) catch return error.InvalidFormat;
|
||||
|
||||
// Percentage value must be between 0-100.
|
||||
if (v > 100) return error.InvalidFormat;
|
||||
|
||||
return .{ .percent = v };
|
||||
} else if (input[input.len - 2] == 'p' and input[input.len - 1] == 'x') {
|
||||
const v = std.fmt.parseInt(
|
||||
u16,
|
||||
input[0 .. input.len - 2],
|
||||
10,
|
||||
) catch return error.InvalidFormat;
|
||||
|
||||
return .{ .pixel = v };
|
||||
}
|
||||
|
||||
return error.InvalidFormat;
|
||||
}
|
||||
|
||||
pub fn formatBuf(self: Size, buf: []u8) Allocator.Error![]const u8 {
|
||||
return res: {
|
||||
switch (self) {
|
||||
.percent => |v| {
|
||||
break :res std.fmt.bufPrint(buf, "{d}%", .{v});
|
||||
},
|
||||
.pixel => |v| {
|
||||
break :res std.fmt.bufPrint(buf, "{d}px", .{v});
|
||||
},
|
||||
}
|
||||
} catch error.OutOfMemory;
|
||||
}
|
||||
|
||||
pub fn equal(self: Size, other: Size) bool {
|
||||
return std.meta.eql(self, other);
|
||||
}
|
||||
};
|
||||
|
||||
dimensions: std.ArrayListUnmanaged(Size) = .{},
|
||||
dimensions_c: std.ArrayListUnmanaged(Size.C) = .{},
|
||||
|
||||
/// Sync with `ghostty_config_quick_terminal_size_s`
|
||||
pub const C = extern struct {
|
||||
dimensions: [*]Size.C,
|
||||
len: usize,
|
||||
};
|
||||
|
||||
/// Required by Config, for C bindings.
|
||||
pub fn cval(self: *const Self) C {
|
||||
return .{
|
||||
.dimensions = self.dimensions_c.items.ptr,
|
||||
.len = self.dimensions_c.items.len,
|
||||
};
|
||||
}
|
||||
|
||||
/// Required by Config.
|
||||
pub fn clone(self: *const Self, alloc: Allocator) Allocator.Error!Self {
|
||||
return .{
|
||||
.dimensions = try self.dimensions.clone(alloc),
|
||||
.dimensions_c = try self.dimensions_c.clone(alloc),
|
||||
};
|
||||
}
|
||||
|
||||
/// Required by Config.
|
||||
pub fn equal(self: Self, other: Self) bool {
|
||||
const itemsA = self.dimensions.items;
|
||||
const itemsB = other.dimensions.items;
|
||||
if (itemsA.len != itemsB.len) return false;
|
||||
for (itemsA, itemsB) |a, b| {
|
||||
if (!a.equal(b)) return false;
|
||||
} else return true;
|
||||
}
|
||||
|
||||
/// Required by Config.
|
||||
pub fn parseCLI(self: *Self, alloc: Allocator, input: ?[]const u8) !void {
|
||||
const value = input orelse return error.ValueRequired;
|
||||
if (value.len == 0) return error.InvalidFormat;
|
||||
|
||||
self.* = .{};
|
||||
|
||||
var i: usize = 0;
|
||||
var it = std.mem.tokenizeScalar(u8, value, ',');
|
||||
while (it.next()) |part| {
|
||||
i += 1;
|
||||
if (i > 2) return error.InvalidValue;
|
||||
|
||||
const parsed_value = try Size.parseValue(part);
|
||||
try self.dimensions.append(alloc, parsed_value);
|
||||
try self.dimensions_c.append(alloc, parsed_value.cval());
|
||||
}
|
||||
|
||||
if (self.dimensions.items.len == 0) return error.InvalidValue;
|
||||
assert(self.dimensions.items.len == self.dimensions_c.items.len);
|
||||
}
|
||||
|
||||
/// Required by Config, use for config formatted.
|
||||
pub fn formatEntry(self: Self, formatter: anytype) !void {
|
||||
var buf: [1024]u8 = undefined;
|
||||
var fbs = std.io.fixedBufferStream(&buf);
|
||||
var writer = fbs.writer();
|
||||
|
||||
for (self.dimensions.items, 0..) |dim, i| {
|
||||
var dim_buf: [128]u8 = undefined;
|
||||
const dim_str = try dim.formatBuf(&dim_buf);
|
||||
if (i != 0) writer.writeByte(',') catch return error.OutOfMemory;
|
||||
writer.writeAll(dim_str) catch return error.OutOfMemory;
|
||||
}
|
||||
|
||||
try formatter.formatEntry([]const u8, fbs.getWritten());
|
||||
}
|
||||
|
||||
test "parseCLI" {
|
||||
const testing = std.testing;
|
||||
var arena = ArenaAllocator.init(testing.allocator);
|
||||
defer arena.deinit();
|
||||
|
||||
const alloc = arena.allocator();
|
||||
|
||||
var p: Self = .{};
|
||||
|
||||
{
|
||||
var expected_dimensions: [1]Size = .{.{ .pixel = 42 }};
|
||||
|
||||
try p.parseCLI(alloc, "42px");
|
||||
const parsed_dimensions = try p.dimensions.toOwnedSlice(alloc);
|
||||
try testing.expectEqualSlices(Size, &expected_dimensions, parsed_dimensions);
|
||||
}
|
||||
|
||||
{
|
||||
var expected_dimensions = [1]Size{.{ .percent = 15 }};
|
||||
|
||||
try p.parseCLI(alloc, "15%");
|
||||
const parsed_dimensions = try p.dimensions.toOwnedSlice(alloc);
|
||||
try testing.expectEqualSlices(Size, &expected_dimensions, parsed_dimensions);
|
||||
}
|
||||
|
||||
{
|
||||
var expected_dimensions = [_]Size{ .{ .pixel = 4096 }, .{ .percent = 23 } };
|
||||
|
||||
try p.parseCLI(alloc, "4096px,23%");
|
||||
const parsed_dimensions = try p.dimensions.toOwnedSlice(alloc);
|
||||
try testing.expectEqualSlices(Size, &expected_dimensions, parsed_dimensions);
|
||||
}
|
||||
|
||||
{
|
||||
var expected_dimensions = [2]Size{ .{ .percent = 78 }, .{ .pixel = 75 } };
|
||||
|
||||
try p.parseCLI(alloc, "78%,75px");
|
||||
const parsed_dimensions = try p.dimensions.toOwnedSlice(alloc);
|
||||
try testing.expectEqualSlices(Size, &expected_dimensions, parsed_dimensions);
|
||||
}
|
||||
|
||||
try testing.expectError(error.InvalidFormat, p.parseCLI(alloc, ""));
|
||||
try testing.expectError(error.InvalidFormat, p.parseCLI(alloc, "29"));
|
||||
try testing.expectError(error.InvalidFormat, p.parseCLI(alloc, "120%"));
|
||||
try testing.expectError(error.InvalidFormat, p.parseCLI(alloc, "65px,12"));
|
||||
}
|
||||
|
||||
test "formatEntry on one-dimension size" {
|
||||
const testing = std.testing;
|
||||
|
||||
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||
defer buf.deinit();
|
||||
|
||||
var arena = ArenaAllocator.init(testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
var p: Self = .{};
|
||||
try p.parseCLI(alloc, "1024px");
|
||||
try p.formatEntry(formatterpkg.entryFormatter("v", buf.writer()));
|
||||
try testing.expectEqualSlices(u8, "v = 1024px\n", buf.items);
|
||||
}
|
||||
|
||||
test "formatEntry on two-dimension size" {
|
||||
const testing = std.testing;
|
||||
|
||||
var buf = std.ArrayList(u8).init(testing.allocator);
|
||||
defer buf.deinit();
|
||||
|
||||
var arena = ArenaAllocator.init(testing.allocator);
|
||||
defer arena.deinit();
|
||||
const alloc = arena.allocator();
|
||||
|
||||
var p: Self = .{};
|
||||
try p.parseCLI(alloc, "1024px,80%");
|
||||
try p.formatEntry(formatterpkg.entryFormatter("v", buf.writer()));
|
||||
try testing.expectEqualSlices(u8, "v = 1024px,80%\n", buf.items);
|
||||
}
|
||||
};
|
||||
|
||||
/// See grapheme-width-method
|
||||
pub const GraphemeWidthMethod = enum {
|
||||
legacy,
|
||||
|
Reference in New Issue
Block a user