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_surface", menuItem: self.menuClose)
|
||||||
syncMenuShortcut(action: "close_window", menuItem: self.menuCloseWindow)
|
syncMenuShortcut(action: "close_window", menuItem: self.menuCloseWindow)
|
||||||
syncMenuShortcut(action: "close_all_windows", menuItem: self.menuCloseAllWindows)
|
syncMenuShortcut(action: "close_all_windows", menuItem: self.menuCloseAllWindows)
|
||||||
syncMenuShortcut(action: "new_split:right", menuItem: self.menuSplitRight)
|
syncMenuShortcut(action: "new_split:right,50%", menuItem: self.menuSplitRight)
|
||||||
syncMenuShortcut(action: "new_split:down", menuItem: self.menuSplitDown)
|
syncMenuShortcut(action: "new_split:down,50%", menuItem: self.menuSplitDown)
|
||||||
|
|
||||||
syncMenuShortcut(action: "copy_to_clipboard", menuItem: self.menuCopy)
|
syncMenuShortcut(action: "copy_to_clipboard", menuItem: self.menuCopy)
|
||||||
syncMenuShortcut(action: "paste_from_clipboard", menuItem: self.menuPaste)
|
syncMenuShortcut(action: "paste_from_clipboard", menuItem: self.menuPaste)
|
||||||
|
@ -3919,18 +3919,25 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|||||||
.{ .amount = position },
|
.{ .amount = position },
|
||||||
),
|
),
|
||||||
|
|
||||||
.new_split => |direction| try self.rt_app.performAction(
|
.new_split => |value| try self.rt_app.performAction(
|
||||||
.{ .surface = self },
|
.{ .surface = self },
|
||||||
.new_split,
|
.new_split,
|
||||||
switch (direction) {
|
.{
|
||||||
.right => .right,
|
.percent = std.fmt.parseInt(
|
||||||
.left => .left,
|
u16,
|
||||||
.down => .down,
|
value[1],
|
||||||
.up => .up,
|
10,
|
||||||
.auto => if (self.screen_size.width > self.screen_size.height)
|
) catch return error.InvalidType,
|
||||||
.right
|
.direction = switch (value[0]) {
|
||||||
else
|
.right => .right,
|
||||||
.down,
|
.left => .left,
|
||||||
|
.down => .down,
|
||||||
|
.up => .up,
|
||||||
|
.auto => if (self.screen_size.width > self.screen_size.height)
|
||||||
|
.right
|
||||||
|
else
|
||||||
|
.down,
|
||||||
|
},
|
||||||
},
|
},
|
||||||
),
|
),
|
||||||
|
|
||||||
|
@ -282,13 +282,19 @@ pub const Action = union(Key) {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is made extern (c_int) to make interop easier with our embedded
|
/// The percentage and direction to create the split
|
||||||
// runtime. The small size cost doesn't make a difference in our union.
|
pub const SplitDirection = extern struct {
|
||||||
pub const SplitDirection = enum(c_int) {
|
percent: u16,
|
||||||
right,
|
direction: Direction,
|
||||||
down,
|
|
||||||
left,
|
// This is made extern (c_int) to make interop easier with our embedded
|
||||||
up,
|
// runtime. The small size cost doesn't make a difference in our union.
|
||||||
|
pub const Direction = enum(c_int) {
|
||||||
|
right,
|
||||||
|
down,
|
||||||
|
left,
|
||||||
|
up,
|
||||||
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
// This is made extern (c_int) to make interop easier with our embedded
|
// This is made extern (c_int) to make interop easier with our embedded
|
||||||
|
@ -548,13 +548,13 @@ fn moveTab(_: *App, target: apprt.Target, move_tab: apprt.action.MoveTab) void {
|
|||||||
fn newSplit(
|
fn newSplit(
|
||||||
self: *App,
|
self: *App,
|
||||||
target: apprt.Target,
|
target: apprt.Target,
|
||||||
direction: apprt.action.SplitDirection,
|
new_split: apprt.action.SplitDirection,
|
||||||
) !void {
|
) !void {
|
||||||
switch (target) {
|
switch (target) {
|
||||||
.app => {},
|
.app => {},
|
||||||
.surface => |v| {
|
.surface => |v| {
|
||||||
const alloc = self.core_app.alloc;
|
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.close", .{ .close_surface = {} });
|
||||||
try self.syncActionAccelerator("win.new_window", .{ .new_window = {} });
|
try self.syncActionAccelerator("win.new_window", .{ .new_window = {} });
|
||||||
try self.syncActionAccelerator("win.new_tab", .{ .new_tab = {} });
|
try self.syncActionAccelerator("win.new_tab", .{ .new_tab = {} });
|
||||||
try self.syncActionAccelerator("win.split_right", .{ .new_split = .right });
|
try self.syncActionAccelerator("win.split_right", .{ .new_split = .{ .right, "50%" } });
|
||||||
try self.syncActionAccelerator("win.split_down", .{ .new_split = .down });
|
try self.syncActionAccelerator("win.split_down", .{ .new_split = .{ .down, "50%" } });
|
||||||
try self.syncActionAccelerator("win.split_left", .{ .new_split = .left });
|
try self.syncActionAccelerator("win.split_left", .{ .new_split = .{ .left, "50%" } });
|
||||||
try self.syncActionAccelerator("win.split_up", .{ .new_split = .up });
|
try self.syncActionAccelerator("win.split_up", .{ .new_split = .{ .up, "50%" } });
|
||||||
try self.syncActionAccelerator("win.copy", .{ .copy_to_clipboard = {} });
|
try self.syncActionAccelerator("win.copy", .{ .copy_to_clipboard = {} });
|
||||||
try self.syncActionAccelerator("win.paste", .{ .paste_from_clipboard = {} });
|
try self.syncActionAccelerator("win.paste", .{ .paste_from_clipboard = {} });
|
||||||
try self.syncActionAccelerator("win.reset", .{ .reset = {} });
|
try self.syncActionAccelerator("win.reset", .{ .reset = {} });
|
||||||
|
@ -20,7 +20,7 @@ pub const Orientation = enum {
|
|||||||
horizontal,
|
horizontal,
|
||||||
vertical,
|
vertical,
|
||||||
|
|
||||||
pub fn fromDirection(direction: apprt.action.SplitDirection) Orientation {
|
pub fn fromDirection(direction: apprt.action.SplitDirection.Direction) Orientation {
|
||||||
return switch (direction) {
|
return switch (direction) {
|
||||||
.right, .left => .horizontal,
|
.right, .left => .horizontal,
|
||||||
.down, .up => .vertical,
|
.down, .up => .vertical,
|
||||||
@ -57,18 +57,18 @@ bottom_right: Surface.Container.Elem,
|
|||||||
pub fn create(
|
pub fn create(
|
||||||
alloc: Allocator,
|
alloc: Allocator,
|
||||||
sibling: *Surface,
|
sibling: *Surface,
|
||||||
direction: apprt.action.SplitDirection,
|
new_split: apprt.action.SplitDirection,
|
||||||
) !*Split {
|
) !*Split {
|
||||||
var split = try alloc.create(Split);
|
var split = try alloc.create(Split);
|
||||||
errdefer alloc.destroy(split);
|
errdefer alloc.destroy(split);
|
||||||
try split.init(sibling, direction);
|
try split.init(sibling, new_split);
|
||||||
return split;
|
return split;
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn init(
|
pub fn init(
|
||||||
self: *Split,
|
self: *Split,
|
||||||
sibling: *Surface,
|
sibling: *Surface,
|
||||||
direction: apprt.action.SplitDirection,
|
new_split: apprt.action.SplitDirection,
|
||||||
) !void {
|
) !void {
|
||||||
// Create the new child surface for the other direction.
|
// Create the new child surface for the other direction.
|
||||||
const alloc = sibling.app.core_app.alloc;
|
const alloc = sibling.app.core_app.alloc;
|
||||||
@ -79,7 +79,7 @@ pub fn init(
|
|||||||
sibling.dimSurface();
|
sibling.dimSurface();
|
||||||
|
|
||||||
// Create the actual GTKPaned, attach the proper children.
|
// 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,
|
.right, .left => c.GTK_ORIENTATION_HORIZONTAL,
|
||||||
.down, .up => c.GTK_ORIENTATION_VERTICAL,
|
.down, .up => c.GTK_ORIENTATION_VERTICAL,
|
||||||
};
|
};
|
||||||
@ -94,7 +94,7 @@ pub fn init(
|
|||||||
// we're inheriting its parent. The sibling points to its location
|
// we're inheriting its parent. The sibling points to its location
|
||||||
// in the split, and the surface points to the other location.
|
// in the split, and the surface points to the other location.
|
||||||
const container = sibling.container;
|
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: {
|
.right, .down => right_down: {
|
||||||
sibling.container = .{ .split_tl = &self.top_left };
|
sibling.container = .{ .split_tl = &self.top_left };
|
||||||
surface.container = .{ .split_br = &self.bottom_right };
|
surface.container = .{ .split_br = &self.bottom_right };
|
||||||
@ -113,7 +113,7 @@ pub fn init(
|
|||||||
.container = container,
|
.container = container,
|
||||||
.top_left = .{ .surface = tl },
|
.top_left = .{ .surface = tl },
|
||||||
.bottom_right = .{ .surface = br },
|
.bottom_right = .{ .surface = br },
|
||||||
.orientation = Orientation.fromDirection(direction),
|
.orientation = Orientation.fromDirection(new_split.direction),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Replace the previous containers element with our split.
|
// Replace the previous containers element with our split.
|
||||||
@ -125,6 +125,25 @@ pub fn init(
|
|||||||
// added to the paned.
|
// added to the paned.
|
||||||
self.updateChildren();
|
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
|
// The new surface should always grab focus
|
||||||
surface.grabFocus();
|
surface.grabFocus();
|
||||||
}
|
}
|
||||||
|
@ -811,7 +811,7 @@ fn gtkActionSplitRight(
|
|||||||
) callconv(.C) void {
|
) callconv(.C) void {
|
||||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
||||||
const surface = self.actionSurface() 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});
|
log.warn("error performing binding action error={}", .{err});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -824,7 +824,7 @@ fn gtkActionSplitDown(
|
|||||||
) callconv(.C) void {
|
) callconv(.C) void {
|
||||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
||||||
const surface = self.actionSurface() 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});
|
log.warn("error performing binding action error={}", .{err});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -837,7 +837,7 @@ fn gtkActionSplitLeft(
|
|||||||
) callconv(.C) void {
|
) callconv(.C) void {
|
||||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
||||||
const surface = self.actionSurface() 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});
|
log.warn("error performing binding action error={}", .{err});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
@ -850,7 +850,7 @@ fn gtkActionSplitUp(
|
|||||||
) callconv(.C) void {
|
) callconv(.C) void {
|
||||||
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
const self: *Window = @ptrCast(@alignCast(ud orelse return));
|
||||||
const surface = self.actionSurface() 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});
|
log.warn("error performing binding action error={}", .{err});
|
||||||
return;
|
return;
|
||||||
};
|
};
|
||||||
|
@ -1908,12 +1908,12 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config {
|
|||||||
try result.keybind.set.put(
|
try result.keybind.set.put(
|
||||||
alloc,
|
alloc,
|
||||||
.{ .key = .{ .translated = .o }, .mods = .{ .ctrl = true, .shift = true } },
|
.{ .key = .{ .translated = .o }, .mods = .{ .ctrl = true, .shift = true } },
|
||||||
.{ .new_split = .right },
|
.{ .new_split = .{ .right, "50%" } },
|
||||||
);
|
);
|
||||||
try result.keybind.set.put(
|
try result.keybind.set.put(
|
||||||
alloc,
|
alloc,
|
||||||
.{ .key = .{ .translated = .e }, .mods = .{ .ctrl = true, .shift = true } },
|
.{ .key = .{ .translated = .e }, .mods = .{ .ctrl = true, .shift = true } },
|
||||||
.{ .new_split = .down },
|
.{ .new_split = .{ .down, "50%" } },
|
||||||
);
|
);
|
||||||
try result.keybind.set.put(
|
try result.keybind.set.put(
|
||||||
alloc,
|
alloc,
|
||||||
@ -2171,12 +2171,12 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config {
|
|||||||
try result.keybind.set.put(
|
try result.keybind.set.put(
|
||||||
alloc,
|
alloc,
|
||||||
.{ .key = .{ .translated = .d }, .mods = .{ .super = true } },
|
.{ .key = .{ .translated = .d }, .mods = .{ .super = true } },
|
||||||
.{ .new_split = .right },
|
.{ .new_split = .{ .right, "50%" } },
|
||||||
);
|
);
|
||||||
try result.keybind.set.put(
|
try result.keybind.set.put(
|
||||||
alloc,
|
alloc,
|
||||||
.{ .key = .{ .translated = .d }, .mods = .{ .super = true, .shift = true } },
|
.{ .key = .{ .translated = .d }, .mods = .{ .super = true, .shift = true } },
|
||||||
.{ .new_split = .down },
|
.{ .new_split = .{ .down, "50%" } },
|
||||||
);
|
);
|
||||||
try result.keybind.set.put(
|
try result.keybind.set.put(
|
||||||
alloc,
|
alloc,
|
||||||
|
@ -310,9 +310,9 @@ pub const Action = union(enum) {
|
|||||||
/// This only works with libadwaita enabled currently.
|
/// This only works with libadwaita enabled currently.
|
||||||
toggle_tab_overview: void,
|
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.
|
/// the direction given.
|
||||||
new_split: SplitDirection,
|
new_split: SplitParameter,
|
||||||
|
|
||||||
/// Focus on a split in a given direction.
|
/// Focus on a split in a given direction.
|
||||||
goto_split: SplitFocusDirection,
|
goto_split: SplitFocusDirection,
|
||||||
@ -454,6 +454,13 @@ pub const Action = union(enum) {
|
|||||||
auto, // splits along the larger direction
|
auto, // splits along the larger direction
|
||||||
};
|
};
|
||||||
|
|
||||||
|
pub const Percentage = []const u8;
|
||||||
|
|
||||||
|
pub const SplitParameter = struct {
|
||||||
|
SplitDirection,
|
||||||
|
Percentage,
|
||||||
|
};
|
||||||
|
|
||||||
pub const SplitFocusDirection = enum {
|
pub const SplitFocusDirection = enum {
|
||||||
previous,
|
previous,
|
||||||
next,
|
next,
|
||||||
@ -500,6 +507,14 @@ pub const Action = union(enum) {
|
|||||||
return std.fmt.parseFloat(T, value) catch return Error.InvalidFormat;
|
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(
|
fn parseParameter(
|
||||||
comptime field: std.builtin.Type.UnionField,
|
comptime field: std.builtin.Type.UnionField,
|
||||||
param: []const u8,
|
param: []const u8,
|
||||||
@ -521,6 +536,7 @@ pub const Action = union(enum) {
|
|||||||
.Enum => try parseEnum(field_.type, next),
|
.Enum => try parseEnum(field_.type, next),
|
||||||
.Int => try parseInt(field_.type, next),
|
.Int => try parseInt(field_.type, next),
|
||||||
.Float => try parseFloat(field_.type, next),
|
.Float => try parseFloat(field_.type, next),
|
||||||
|
.Pointer => if (field_.type == Percentage) try parsePercentage(next),
|
||||||
else => unreachable,
|
else => unreachable,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@ -1688,10 +1704,21 @@ test "parse: action with enum" {
|
|||||||
|
|
||||||
// parameter
|
// 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.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" {
|
test "parse: action with int" {
|
||||||
|
Reference in New Issue
Block a user