From cd01340cce63ebfdd1bc7fb331f97f37580f87a2 Mon Sep 17 00:00:00 2001 From: Gregory Anders Date: Tue, 7 Nov 2023 16:15:46 -0600 Subject: [PATCH 1/2] macos: add key binding for equalizing split sizes --- include/ghostty.h | 3 +++ macos/Sources/AppDelegate.swift | 2 ++ .../Terminal/TerminalController.swift | 5 ++++ macos/Sources/Ghostty/AppState.swift | 11 ++++++++ macos/Sources/Ghostty/Ghostty.SplitNode.swift | 26 ++++++++++++++++++- .../Ghostty/Ghostty.TerminalSplit.swift | 14 +++++++++- macos/Sources/Ghostty/Package.swift | 3 +++ macos/Sources/MainMenu.xib | 12 +++++++-- src/Surface.zig | 6 +++++ src/apprt/embedded.zig | 17 ++++++++++++ src/config/Config.zig | 5 ++++ src/input/Binding.zig | 3 +++ 12 files changed, 103 insertions(+), 4 deletions(-) diff --git a/include/ghostty.h b/include/ghostty.h index 4bb31d655..ac1077092 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -341,6 +341,7 @@ typedef void (*ghostty_runtime_control_inspector_cb)(void *, ghostty_inspector_m typedef void (*ghostty_runtime_close_surface_cb)(void *, bool); typedef void (*ghostty_runtime_focus_split_cb)(void *, ghostty_split_focus_direction_e); typedef void (*ghostty_runtime_resize_split_cb)(void *, ghostty_split_resize_direction_e, uint16_t); +typedef void (*ghostty_runtime_equalize_splits_cb)(void *); typedef void (*ghostty_runtime_toggle_split_zoom_cb)(void *); typedef void (*ghostty_runtime_goto_tab_cb)(void *, int32_t); typedef void (*ghostty_runtime_toggle_fullscreen_cb)(void *, ghostty_non_native_fullscreen_e); @@ -366,6 +367,7 @@ typedef struct { ghostty_runtime_close_surface_cb close_surface_cb; ghostty_runtime_focus_split_cb focus_split_cb; ghostty_runtime_resize_split_cb resize_split_cb; + ghostty_runtime_equalize_splits_cb equalize_splits_cb; ghostty_runtime_toggle_split_zoom_cb toggle_split_zoom_cb; ghostty_runtime_goto_tab_cb goto_tab_cb; ghostty_runtime_toggle_fullscreen_cb toggle_fullscreen_cb; @@ -422,6 +424,7 @@ void ghostty_surface_request_close(ghostty_surface_t); void ghostty_surface_split(ghostty_surface_t, ghostty_split_direction_e); void ghostty_surface_split_focus(ghostty_surface_t, ghostty_split_focus_direction_e); void ghostty_surface_split_resize(ghostty_surface_t, ghostty_split_resize_direction_e, uint16_t); +void ghostty_surface_split_equalize(ghostty_surface_t); bool ghostty_surface_binding_action(ghostty_surface_t, const char *, uintptr_t); void ghostty_surface_complete_clipboard_request(ghostty_surface_t, const char *, void *, bool); diff --git a/macos/Sources/AppDelegate.swift b/macos/Sources/AppDelegate.swift index c57055ad1..00cfd8479 100644 --- a/macos/Sources/AppDelegate.swift +++ b/macos/Sources/AppDelegate.swift @@ -38,6 +38,7 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp @IBOutlet private var menuResetFontSize: NSMenuItem? @IBOutlet private var menuTerminalInspector: NSMenuItem? + @IBOutlet private var menuEqualizeSplits: NSMenuItem? @IBOutlet private var menuMoveSplitDividerUp: NSMenuItem? @IBOutlet private var menuMoveSplitDividerDown: NSMenuItem? @IBOutlet private var menuMoveSplitDividerLeft: NSMenuItem? @@ -215,6 +216,7 @@ class AppDelegate: NSObject, ObservableObject, NSApplicationDelegate, GhosttyApp syncMenuShortcut(action: "resize_split:down,10", menuItem: self.menuMoveSplitDividerDown) syncMenuShortcut(action: "resize_split:right,10", menuItem: self.menuMoveSplitDividerRight) syncMenuShortcut(action: "resize_split:left,10", menuItem: self.menuMoveSplitDividerLeft) + syncMenuShortcut(action: "equalize_splits", menuItem: self.menuEqualizeSplits) syncMenuShortcut(action: "increase_font_size:1", menuItem: self.menuIncreaseFontSize) syncMenuShortcut(action: "decrease_font_size:1", menuItem: self.menuDecreaseFontSize) diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index e95adb0ab..3355fef2d 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -271,6 +271,11 @@ class TerminalController: NSWindowController, NSWindowDelegate, splitMoveFocus(direction: .right) } + @IBAction func equalizeSplits(_ sender: Any) { + guard let surface = focusedSurface?.surface else { return } + ghostty.splitEqualize(surface: surface) + } + @IBAction func moveSplitDividerUp(_ sender: Any) { guard let surface = focusedSurface?.surface else { return } ghostty.splitResize(surface: surface, direction: .up, amount: 10) diff --git a/macos/Sources/Ghostty/AppState.swift b/macos/Sources/Ghostty/AppState.swift index 11a390ae9..0ecfd819e 100644 --- a/macos/Sources/Ghostty/AppState.swift +++ b/macos/Sources/Ghostty/AppState.swift @@ -160,6 +160,8 @@ extension Ghostty { focus_split_cb: { userdata, direction in AppState.focusSplit(userdata, direction: direction) }, resize_split_cb: { userdata, direction, amount in AppState.resizeSplit(userdata, direction: direction, amount: amount) }, + equalize_splits_cb: { userdata in + AppState.equalizeSplits(userdata) }, toggle_split_zoom_cb: { userdata in AppState.toggleSplitZoom(userdata) }, goto_tab_cb: { userdata, n in AppState.gotoTab(userdata, n: n) }, toggle_fullscreen_cb: { userdata, nonNativeFullscreen in AppState.toggleFullscreen(userdata, nonNativeFullscreen: nonNativeFullscreen) }, @@ -298,6 +300,10 @@ extension Ghostty { ghostty_surface_split_resize(surface, direction.toNative(), amount) } + func splitEqualize(surface: ghostty_surface_t) { + ghostty_surface_split_equalize(surface) + } + func splitToggleZoom(surface: ghostty_surface_t) { let action = "toggle_split_zoom" if (!ghostty_surface_binding_action(surface, action, UInt(action.count))) { @@ -383,6 +389,11 @@ extension Ghostty { ) } + static func equalizeSplits(_ userdata: UnsafeMutableRawPointer?) { + guard let surface = self.surfaceUserdata(from: userdata) else { return } + NotificationCenter.default.post(name: Notification.didEqualizeSplits, object: surface) + } + static func toggleSplitZoom(_ userdata: UnsafeMutableRawPointer?) { guard let surface = self.surfaceUserdata(from: userdata) else { return } diff --git a/macos/Sources/Ghostty/Ghostty.SplitNode.swift b/macos/Sources/Ghostty/Ghostty.SplitNode.swift index fd8a1e7d3..13e742441 100644 --- a/macos/Sources/Ghostty/Ghostty.SplitNode.swift +++ b/macos/Sources/Ghostty/Ghostty.SplitNode.swift @@ -171,7 +171,31 @@ extension Ghostty { } } - + /// Equalize the splits in this container. Each split is equalized + /// based on its weight, i.e. the number of leaves it contains. + /// This function returns the weight of this container. + func equalize() -> UInt { + let topLeftWeight: UInt + switch (topLeft) { + case .leaf: + topLeftWeight = 1 + case .split(let c): + topLeftWeight = c.equalize() + } + + let bottomRightWeight: UInt + switch (bottomRight) { + case .leaf: + bottomRightWeight = 1 + case .split(let c): + bottomRightWeight = c.equalize() + } + + let weight = topLeftWeight + bottomRightWeight + split = Double(topLeftWeight) / Double(weight) + return weight + } + // MARK: - Hashable func hash(into hasher: inout Hasher) { diff --git a/macos/Sources/Ghostty/Ghostty.TerminalSplit.swift b/macos/Sources/Ghostty/Ghostty.TerminalSplit.swift index 5ea382c86..9127a2e13 100644 --- a/macos/Sources/Ghostty/Ghostty.TerminalSplit.swift +++ b/macos/Sources/Ghostty/Ghostty.TerminalSplit.swift @@ -50,6 +50,7 @@ extension Ghostty { var body: some View { let center = NotificationCenter.default let pubZoom = center.publisher(for: Notification.didToggleSplitZoom) + let pubEqualize = center.publisher(for: Notification.didEqualizeSplits) // If we're zoomed, we don't render anything, we are transparent. This // ensures that the View stays around so we don't lose our state, but @@ -75,6 +76,7 @@ extension Ghostty { container: container ) .onReceive(pubZoom) { onZoom(notification: $0) } + .onReceive(pubEqualize) { onEqualize(notification: $0) } } } .navigationTitle(surfaceTitle ?? "Ghostty") @@ -135,8 +137,18 @@ extension Ghostty { } } } + + func onEqualize(notification: SwiftUI.Notification) { + // If there are no splits then there is nothing to do + switch (node) { + case .split(let c): + _ = c.equalize() + default: + return + } + } } - + /// A noSplit leaf node of a split tree. private struct TerminalSplitLeaf: View { /// The leaf to draw the surface for. diff --git a/macos/Sources/Ghostty/Package.swift b/macos/Sources/Ghostty/Package.swift index 823af9f4b..9b532ea3f 100644 --- a/macos/Sources/Ghostty/Package.swift +++ b/macos/Sources/Ghostty/Package.swift @@ -150,6 +150,9 @@ extension Ghostty.Notification { static let didResizeSplit = Notification.Name("com.mitchellh.ghostty.didResizeSplit") static let ResizeSplitDirectionKey = didResizeSplit.rawValue + ".direction" static let ResizeSplitAmountKey = didResizeSplit.rawValue + ".amount" + + /// Notification sent to the split root to equalize split sizes + static let didEqualizeSplits = Notification.Name("com.mitchellh.ghostty.didEqualizeSplits") } // Make the input enum hashable. diff --git a/macos/Sources/MainMenu.xib b/macos/Sources/MainMenu.xib index e2979cfee..386274cdd 100644 --- a/macos/Sources/MainMenu.xib +++ b/macos/Sources/MainMenu.xib @@ -1,8 +1,8 @@ - + - + @@ -18,6 +18,7 @@ + @@ -270,6 +271,13 @@ + + + + + + + diff --git a/src/Surface.zig b/src/Surface.zig index d7329889b..112344514 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -2458,6 +2458,12 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool } else log.warn("runtime doesn't implement resizeSplit", .{}); }, + .equalize_splits => { + if (@hasDecl(apprt.Surface, "equalizeSplits")) { + self.rt_surface.equalizeSplits(); + } else log.warn("runtime doesn't implement equalizeSplits", .{}); + }, + .toggle_split_zoom => { if (@hasDecl(apprt.Surface, "toggleSplitZoom")) { self.rt_surface.toggleSplitZoom(); diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index dd9556ccb..4042a8dfb 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -95,6 +95,9 @@ pub const App = struct { /// Resize the current split. resize_split: ?*const fn (SurfaceUD, input.SplitResizeDirection, u16) callconv(.C) void = null, + /// Equalize all splits in the current window + equalize_splits: ?*const fn (SurfaceUD) callconv(.C) void = null, + /// Zoom the current split. toggle_split_zoom: ?*const fn (SurfaceUD) callconv(.C) void = null, @@ -396,6 +399,15 @@ pub const Surface = struct { func(self.opts.userdata, direction, amount); } + pub fn equalizeSplits(self: *const Surface) void { + const func = self.app.opts.equalize_splits orelse { + log.info("runtime embedder does not support equalize splits", .{}); + return; + }; + + func(self.opts.userdata); + } + pub fn toggleSplitZoom(self: *const Surface) void { const func = self.app.opts.toggle_split_zoom orelse { log.info("runtime embedder does not support split zoom", .{}); @@ -1388,6 +1400,11 @@ pub const CAPI = struct { ptr.resizeSplit(direction, amount); } + /// Equalize the size of all splits in the current window. + export fn ghostty_surface_split_equalize(ptr: *Surface) void { + ptr.equalizeSplits(); + } + /// Invoke an action on the surface. export fn ghostty_surface_binding_action( ptr: *Surface, diff --git a/src/config/Config.zig b/src/config/Config.zig index 1d780ab8c..952ca4bf8 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1016,6 +1016,11 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config { .{ .key = .right, .mods = .{ .super = true, .ctrl = true } }, .{ .resize_split = .{ .right, 10 } }, ); + try result.keybind.set.put( + alloc, + .{ .key = .equal, .mods = .{ .shift = true, .alt = true } }, + .{ .equalize_splits = {} }, + ); // Inspector, matching Chromium try result.keybind.set.put( diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 24fb469a6..25bf9df4c 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -197,6 +197,9 @@ pub const Action = union(enum) { /// direction resize_split: SplitResizeParameter, + /// Equalize all splits in the current window + equalize_splits: void, + /// Show, hide, or toggle the terminal inspector for the currently /// focused terminal. inspector: InspectorMode, From d482fed7f2e7d6433238781abfea9a22b0e61ab0 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Tue, 7 Nov 2023 15:26:41 -0800 Subject: [PATCH 2/2] simplify some swift --- macos/Sources/Ghostty/Ghostty.TerminalSplit.swift | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/macos/Sources/Ghostty/Ghostty.TerminalSplit.swift b/macos/Sources/Ghostty/Ghostty.TerminalSplit.swift index 9127a2e13..2d4b7881e 100644 --- a/macos/Sources/Ghostty/Ghostty.TerminalSplit.swift +++ b/macos/Sources/Ghostty/Ghostty.TerminalSplit.swift @@ -139,13 +139,8 @@ extension Ghostty { } func onEqualize(notification: SwiftUI.Notification) { - // If there are no splits then there is nothing to do - switch (node) { - case .split(let c): - _ = c.equalize() - default: - return - } + guard case .split(let c) = node else { return } + _ = c.equalize() } }