mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-08-02 14:57:31 +03:00
First draft of feature implementation
This commit is contained in:
@ -312,8 +312,8 @@ class AppDelegate: NSObject,
|
||||
syncMenuShortcut(action: "close_surface", menuItem: self.menuClose)
|
||||
syncMenuShortcut(action: "close_window", menuItem: self.menuCloseWindow)
|
||||
syncMenuShortcut(action: "close_all_windows", menuItem: self.menuCloseAllWindows)
|
||||
syncMenuShortcut(action: "new_split:right", menuItem: self.menuSplitRight)
|
||||
syncMenuShortcut(action: "new_split:down", menuItem: self.menuSplitDown)
|
||||
syncMenuShortcut(action: "new_split:right,50%", menuItem: self.menuSplitRight)
|
||||
syncMenuShortcut(action: "new_split:down,50%", menuItem: self.menuSplitDown)
|
||||
|
||||
syncMenuShortcut(action: "copy_to_clipboard", menuItem: self.menuCopy)
|
||||
syncMenuShortcut(action: "paste_from_clipboard", menuItem: self.menuPaste)
|
||||
|
@ -3919,10 +3919,16 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
.{ .amount = position },
|
||||
),
|
||||
|
||||
.new_split => |direction| try self.rt_app.performAction(
|
||||
.new_split => |value| try self.rt_app.performAction(
|
||||
.{ .surface = self },
|
||||
.new_split,
|
||||
switch (direction) {
|
||||
.{
|
||||
.percent = std.fmt.parseInt(
|
||||
u16,
|
||||
value[1],
|
||||
10,
|
||||
) catch return error.InvalidType,
|
||||
.direction = switch (value[0]) {
|
||||
.right => .right,
|
||||
.left => .left,
|
||||
.down => .down,
|
||||
@ -3932,6 +3938,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||
else
|
||||
.down,
|
||||
},
|
||||
},
|
||||
),
|
||||
|
||||
.goto_split => |direction| try self.rt_app.performAction(
|
||||
|
@ -282,14 +282,20 @@ pub const Action = union(Key) {
|
||||
}
|
||||
};
|
||||
|
||||
/// The percentage and direction to create the split
|
||||
pub const SplitDirection = extern struct {
|
||||
percent: u16,
|
||||
direction: Direction,
|
||||
|
||||
// This is made extern (c_int) to make interop easier with our embedded
|
||||
// runtime. The small size cost doesn't make a difference in our union.
|
||||
pub const SplitDirection = enum(c_int) {
|
||||
pub const Direction = enum(c_int) {
|
||||
right,
|
||||
down,
|
||||
left,
|
||||
up,
|
||||
};
|
||||
};
|
||||
|
||||
// This is made extern (c_int) to make interop easier with our embedded
|
||||
// runtime. The small size cost doesn't make a difference in our union.
|
||||
|
@ -548,13 +548,13 @@ fn moveTab(_: *App, target: apprt.Target, move_tab: apprt.action.MoveTab) void {
|
||||
fn newSplit(
|
||||
self: *App,
|
||||
target: apprt.Target,
|
||||
direction: apprt.action.SplitDirection,
|
||||
new_split: apprt.action.SplitDirection,
|
||||
) !void {
|
||||
switch (target) {
|
||||
.app => {},
|
||||
.surface => |v| {
|
||||
const alloc = self.core_app.alloc;
|
||||
_ = try Split.create(alloc, v.rt_surface, direction);
|
||||
_ = try Split.create(alloc, v.rt_surface, new_split);
|
||||
},
|
||||
}
|
||||
}
|
||||
@ -863,10 +863,10 @@ fn syncActionAccelerators(self: *App) !void {
|
||||
try self.syncActionAccelerator("win.close", .{ .close_surface = {} });
|
||||
try self.syncActionAccelerator("win.new_window", .{ .new_window = {} });
|
||||
try self.syncActionAccelerator("win.new_tab", .{ .new_tab = {} });
|
||||
try self.syncActionAccelerator("win.split_right", .{ .new_split = .right });
|
||||
try self.syncActionAccelerator("win.split_down", .{ .new_split = .down });
|
||||
try self.syncActionAccelerator("win.split_left", .{ .new_split = .left });
|
||||
try self.syncActionAccelerator("win.split_up", .{ .new_split = .up });
|
||||
try self.syncActionAccelerator("win.split_right", .{ .new_split = .{ .right, "50%" } });
|
||||
try self.syncActionAccelerator("win.split_down", .{ .new_split = .{ .down, "50%" } });
|
||||
try self.syncActionAccelerator("win.split_left", .{ .new_split = .{ .left, "50%" } });
|
||||
try self.syncActionAccelerator("win.split_up", .{ .new_split = .{ .up, "50%" } });
|
||||
try self.syncActionAccelerator("win.copy", .{ .copy_to_clipboard = {} });
|
||||
try self.syncActionAccelerator("win.paste", .{ .paste_from_clipboard = {} });
|
||||
try self.syncActionAccelerator("win.reset", .{ .reset = {} });
|
||||
|
@ -20,7 +20,7 @@ pub const Orientation = enum {
|
||||
horizontal,
|
||||
vertical,
|
||||
|
||||
pub fn fromDirection(direction: apprt.action.SplitDirection) Orientation {
|
||||
pub fn fromDirection(direction: apprt.action.SplitDirection.Direction) Orientation {
|
||||
return switch (direction) {
|
||||
.right, .left => .horizontal,
|
||||
.down, .up => .vertical,
|
||||
@ -57,18 +57,18 @@ bottom_right: Surface.Container.Elem,
|
||||
pub fn create(
|
||||
alloc: Allocator,
|
||||
sibling: *Surface,
|
||||
direction: apprt.action.SplitDirection,
|
||||
new_split: apprt.action.SplitDirection,
|
||||
) !*Split {
|
||||
var split = try alloc.create(Split);
|
||||
errdefer alloc.destroy(split);
|
||||
try split.init(sibling, direction);
|
||||
try split.init(sibling, new_split);
|
||||
return split;
|
||||
}
|
||||
|
||||
pub fn init(
|
||||
self: *Split,
|
||||
sibling: *Surface,
|
||||
direction: apprt.action.SplitDirection,
|
||||
new_split: apprt.action.SplitDirection,
|
||||
) !void {
|
||||
// Create the new child surface for the other direction.
|
||||
const alloc = sibling.app.core_app.alloc;
|
||||
@ -79,7 +79,7 @@ pub fn init(
|
||||
sibling.dimSurface();
|
||||
|
||||
// Create the actual GTKPaned, attach the proper children.
|
||||
const orientation: c_uint = switch (direction) {
|
||||
const orientation: c_uint = switch (new_split.direction) {
|
||||
.right, .left => c.GTK_ORIENTATION_HORIZONTAL,
|
||||
.down, .up => c.GTK_ORIENTATION_VERTICAL,
|
||||
};
|
||||
@ -94,7 +94,7 @@ pub fn init(
|
||||
// we're inheriting its parent. The sibling points to its location
|
||||
// in the split, and the surface points to the other location.
|
||||
const container = sibling.container;
|
||||
const tl: *Surface, const br: *Surface = switch (direction) {
|
||||
const tl: *Surface, const br: *Surface = switch (new_split.direction) {
|
||||
.right, .down => right_down: {
|
||||
sibling.container = .{ .split_tl = &self.top_left };
|
||||
surface.container = .{ .split_br = &self.bottom_right };
|
||||
@ -113,7 +113,7 @@ pub fn init(
|
||||
.container = container,
|
||||
.top_left = .{ .surface = tl },
|
||||
.bottom_right = .{ .surface = br },
|
||||
.orientation = Orientation.fromDirection(direction),
|
||||
.orientation = Orientation.fromDirection(new_split.direction),
|
||||
};
|
||||
|
||||
// Replace the previous containers element with our split.
|
||||
@ -125,6 +125,25 @@ pub fn init(
|
||||
// added to the paned.
|
||||
self.updateChildren();
|
||||
|
||||
// Skip resize logic if percentage is 50 (this is the default behavior)
|
||||
if (new_split.percent != 50) {
|
||||
const allocation = sibling.size;
|
||||
const split_percentage: f32 = @as(f32, @floatFromInt(new_split.percent)) / 100;
|
||||
const total_surface_size: f32 = switch (self.orientation) {
|
||||
.horizontal => @floatFromInt(allocation.width),
|
||||
.vertical => @floatFromInt(allocation.height),
|
||||
};
|
||||
|
||||
// percentage to apply based on direction
|
||||
const pct = switch (new_split.direction) {
|
||||
.right, .down => 1 - split_percentage,
|
||||
.left, .up => split_percentage,
|
||||
};
|
||||
|
||||
const divider_position = @as(c_int, @intFromFloat(total_surface_size * pct));
|
||||
c.gtk_paned_set_position(self.paned, divider_position);
|
||||
}
|
||||
|
||||
// The new surface should always grab focus
|
||||
surface.grabFocus();
|
||||
}
|
||||
|
@ -811,7 +811,7 @@ fn gtkActionSplitRight(
|
||||
) callconv(.C) void {
|
||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
||||
const surface = self.actionSurface() orelse return;
|
||||
_ = surface.performBindingAction(.{ .new_split = .right }) catch |err| {
|
||||
_ = surface.performBindingAction(.{ .new_split = .{ .right, "50%" } }) catch |err| {
|
||||
log.warn("error performing binding action error={}", .{err});
|
||||
return;
|
||||
};
|
||||
@ -824,7 +824,7 @@ fn gtkActionSplitDown(
|
||||
) callconv(.C) void {
|
||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
||||
const surface = self.actionSurface() orelse return;
|
||||
_ = surface.performBindingAction(.{ .new_split = .down }) catch |err| {
|
||||
_ = surface.performBindingAction(.{ .new_split = .{ .down, "50%" } }) catch |err| {
|
||||
log.warn("error performing binding action error={}", .{err});
|
||||
return;
|
||||
};
|
||||
@ -837,7 +837,7 @@ fn gtkActionSplitLeft(
|
||||
) callconv(.C) void {
|
||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
||||
const surface = self.actionSurface() orelse return;
|
||||
_ = surface.performBindingAction(.{ .new_split = .left }) catch |err| {
|
||||
_ = surface.performBindingAction(.{ .new_split = .{ .left, "50%" } }) catch |err| {
|
||||
log.warn("error performing binding action error={}", .{err});
|
||||
return;
|
||||
};
|
||||
@ -850,7 +850,7 @@ fn gtkActionSplitUp(
|
||||
) callconv(.C) void {
|
||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
||||
const surface = self.actionSurface() orelse return;
|
||||
_ = surface.performBindingAction(.{ .new_split = .up }) catch |err| {
|
||||
_ = surface.performBindingAction(.{ .new_split = .{ .up, "50%" } }) catch |err| {
|
||||
log.warn("error performing binding action error={}", .{err});
|
||||
return;
|
||||
};
|
||||
|
@ -1908,12 +1908,12 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config {
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .{ .translated = .o }, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .new_split = .right },
|
||||
.{ .new_split = .{ .right, "50%" } },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .{ .translated = .e }, .mods = .{ .ctrl = true, .shift = true } },
|
||||
.{ .new_split = .down },
|
||||
.{ .new_split = .{ .down, "50%" } },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
@ -2171,12 +2171,12 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config {
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .{ .translated = .d }, .mods = .{ .super = true } },
|
||||
.{ .new_split = .right },
|
||||
.{ .new_split = .{ .right, "50%" } },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .{ .translated = .d }, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .new_split = .down },
|
||||
.{ .new_split = .{ .down, "50%" } },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
|
@ -310,9 +310,9 @@ pub const Action = union(enum) {
|
||||
/// This only works with libadwaita enabled currently.
|
||||
toggle_tab_overview: void,
|
||||
|
||||
/// Create a new split in the given direction. The new split will appear in
|
||||
/// Create a new split in the given direction and percentage. The new split will appear in
|
||||
/// the direction given.
|
||||
new_split: SplitDirection,
|
||||
new_split: SplitParameter,
|
||||
|
||||
/// Focus on a split in a given direction.
|
||||
goto_split: SplitFocusDirection,
|
||||
@ -454,6 +454,13 @@ pub const Action = union(enum) {
|
||||
auto, // splits along the larger direction
|
||||
};
|
||||
|
||||
pub const Percentage = []const u8;
|
||||
|
||||
pub const SplitParameter = struct {
|
||||
SplitDirection,
|
||||
Percentage,
|
||||
};
|
||||
|
||||
pub const SplitFocusDirection = enum {
|
||||
previous,
|
||||
next,
|
||||
@ -500,6 +507,14 @@ pub const Action = union(enum) {
|
||||
return std.fmt.parseFloat(T, value) catch return Error.InvalidFormat;
|
||||
}
|
||||
|
||||
fn parsePercentage(value: []const u8) !Percentage {
|
||||
if (value.len < 2) return Error.InvalidFormat;
|
||||
if (value[value.len - 1] != '%') return Error.InvalidFormat;
|
||||
const percent = value[0 .. value.len - 1];
|
||||
_ = std.fmt.parseInt(u16, percent, 10) catch return Error.InvalidFormat;
|
||||
return percent;
|
||||
}
|
||||
|
||||
fn parseParameter(
|
||||
comptime field: std.builtin.Type.UnionField,
|
||||
param: []const u8,
|
||||
@ -521,6 +536,7 @@ pub const Action = union(enum) {
|
||||
.Enum => try parseEnum(field_.type, next),
|
||||
.Int => try parseInt(field_.type, next),
|
||||
.Float => try parseFloat(field_.type, next),
|
||||
.Pointer => if (field_.type == Percentage) try parsePercentage(next),
|
||||
else => unreachable,
|
||||
};
|
||||
}
|
||||
@ -1688,10 +1704,21 @@ test "parse: action with enum" {
|
||||
|
||||
// parameter
|
||||
{
|
||||
const binding = try parseSingle("a=new_split:right");
|
||||
// Note: The "50%" in the binding string gets parsed to just "50"
|
||||
const binding = try parseSingle("a=new_split:right,50%");
|
||||
try testing.expect(binding.action == .new_split);
|
||||
try testing.expectEqual(Action.SplitDirection.right, binding.action.new_split);
|
||||
try testing.expectEqual(Action.SplitDirection.right, binding.action.new_split[0]);
|
||||
try testing.expectEqualSlices(u8, "50", binding.action.new_split[1]);
|
||||
}
|
||||
|
||||
// missing unit %
|
||||
try testing.expectError(Error.InvalidFormat, parseSingle("a=new_split:right,50"));
|
||||
|
||||
// too many
|
||||
try testing.expectError(Error.InvalidFormat, parseSingle("a=new_split:right,30%,50%"));
|
||||
|
||||
// invalid type
|
||||
try testing.expectError(Error.InvalidFormat, parseSingle("a=new_split:right,0.5%"));
|
||||
}
|
||||
|
||||
test "parse: action with int" {
|
||||
|
Reference in New Issue
Block a user