mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-24 12:46:10 +03:00
Merge pull request #92 from mitchellh/split-nav
macos: split keyboard navigation up/down/left/right and previous/next
This commit is contained in:
@ -32,6 +32,7 @@ typedef const char* (*ghostty_runtime_read_clipboard_cb)(void *);
|
||||
typedef void (*ghostty_runtime_write_clipboard_cb)(void *, const char *);
|
||||
typedef void (*ghostty_runtime_new_split_cb)(void *, ghostty_split_direction_e);
|
||||
typedef void (*ghostty_runtime_close_surface_cb)(void *);
|
||||
typedef void (*ghostty_runtime_focus_split_cb)(void *, ghostty_split_focus_direction_e);
|
||||
|
||||
typedef struct {
|
||||
void *userdata;
|
||||
@ -41,6 +42,7 @@ typedef struct {
|
||||
ghostty_runtime_write_clipboard_cb write_clipboard_cb;
|
||||
ghostty_runtime_new_split_cb new_split_cb;
|
||||
ghostty_runtime_close_surface_cb close_surface_cb;
|
||||
ghostty_runtime_focus_split_cb focus_split_cb;
|
||||
} ghostty_runtime_config_s;
|
||||
|
||||
typedef struct {
|
||||
@ -54,6 +56,15 @@ typedef enum {
|
||||
GHOSTTY_SPLIT_DOWN
|
||||
} ghostty_split_direction_e;
|
||||
|
||||
typedef enum {
|
||||
GHOSTTY_SPLIT_FOCUS_PREVIOUS,
|
||||
GHOSTTY_SPLIT_FOCUS_NEXT,
|
||||
GHOSTTY_SPLIT_FOCUS_TOP,
|
||||
GHOSTTY_SPLIT_FOCUS_LEFT,
|
||||
GHOSTTY_SPLIT_FOCUS_BOTTOM,
|
||||
GHOSTTY_SPLIT_FOCUS_RIGHT,
|
||||
} ghostty_split_focus_direction_e;
|
||||
|
||||
typedef enum {
|
||||
GHOSTTY_MOUSE_RELEASE,
|
||||
GHOSTTY_MOUSE_PRESS,
|
||||
@ -254,6 +265,7 @@ void ghostty_surface_mouse_scroll(ghostty_surface_t, double, double);
|
||||
void ghostty_surface_ime_point(ghostty_surface_t, double *, double *);
|
||||
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);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
|
@ -58,7 +58,9 @@ extension Ghostty {
|
||||
read_clipboard_cb: { userdata in AppState.readClipboard(userdata) },
|
||||
write_clipboard_cb: { userdata, str in AppState.writeClipboard(userdata, string: str) },
|
||||
new_split_cb: { userdata, direction in AppState.newSplit(userdata, direction: ghostty_split_direction_e(UInt32(direction))) },
|
||||
close_surface_cb: { userdata in AppState.closeSurface(userdata) }
|
||||
close_surface_cb: { userdata in AppState.closeSurface(userdata) },
|
||||
focus_split_cb: { userdata, direction in
|
||||
AppState.focusSplit(userdata, direction: ghostty_split_focus_direction_e(UInt32(direction))) }
|
||||
)
|
||||
|
||||
// Create the ghostty app.
|
||||
@ -92,6 +94,10 @@ extension Ghostty {
|
||||
ghostty_surface_split(surface, direction)
|
||||
}
|
||||
|
||||
func splitMoveFocus(surface: ghostty_surface_t, direction: SplitFocusDirection) {
|
||||
ghostty_surface_split_focus(surface, direction.toNative())
|
||||
}
|
||||
|
||||
// MARK: Ghostty Callbacks
|
||||
|
||||
static func newSplit(_ userdata: UnsafeMutableRawPointer?, direction: ghostty_split_direction_e) {
|
||||
@ -106,6 +112,18 @@ extension Ghostty {
|
||||
NotificationCenter.default.post(name: Notification.ghosttyCloseSurface, object: surface)
|
||||
}
|
||||
|
||||
static func focusSplit(_ userdata: UnsafeMutableRawPointer?, direction: ghostty_split_focus_direction_e) {
|
||||
guard let surface = self.surfaceUserdata(from: userdata) else { return }
|
||||
guard let splitDirection = SplitFocusDirection.from(direction: direction) else { return }
|
||||
NotificationCenter.default.post(
|
||||
name: Notification.ghosttyFocusSplit,
|
||||
object: surface,
|
||||
userInfo: [
|
||||
Notification.SplitDirectionKey: splitDirection,
|
||||
]
|
||||
)
|
||||
}
|
||||
|
||||
static func readClipboard(_ userdata: UnsafeMutableRawPointer?) -> UnsafePointer<CChar>? {
|
||||
guard let appState = self.appState(fromSurface: userdata) else { return nil }
|
||||
guard let str = NSPasteboard.general.string(forType: .string) else { return nil }
|
||||
|
@ -73,6 +73,49 @@ extension Ghostty {
|
||||
self.bottomRight = .noSplit(.init(app))
|
||||
}
|
||||
}
|
||||
|
||||
/// This keeps track of the "neighbors" of a split: the immediately above/below/left/right
|
||||
/// nodes. This is purposely weak so we don't have to worry about memory management
|
||||
/// with this (although, it should always be correct).
|
||||
struct Neighbors {
|
||||
var left: SplitNode?
|
||||
var right: SplitNode?
|
||||
var top: SplitNode?
|
||||
var bottom: SplitNode?
|
||||
|
||||
/// These are the previous/next nodes. It will certainly be one of the above as well
|
||||
/// but we keep track of these separately because depending on the split direction
|
||||
/// of the containing node, previous may be left OR top (same for next).
|
||||
var previous: SplitNode?
|
||||
var next: SplitNode?
|
||||
|
||||
/// No neighbors, used by the root node.
|
||||
static let empty: Self = .init()
|
||||
|
||||
/// Get the node for a given direction.
|
||||
func get(direction: SplitFocusDirection) -> SplitNode? {
|
||||
let map: [SplitFocusDirection : KeyPath<Self, SplitNode?>] = [
|
||||
.previous: \.previous,
|
||||
.next: \.next,
|
||||
.top: \.top,
|
||||
.bottom: \.bottom,
|
||||
.left: \.left,
|
||||
.right: \.right,
|
||||
]
|
||||
|
||||
guard let path = map[direction] else { return nil }
|
||||
return self[keyPath: path]
|
||||
}
|
||||
|
||||
/// Update multiple keys and return a new copy.
|
||||
func update(_ attrs: [WritableKeyPath<Self, SplitNode?>: SplitNode?]) -> Self {
|
||||
var clone = self
|
||||
attrs.forEach { (key, value) in
|
||||
clone[keyPath: key] = value
|
||||
}
|
||||
return clone
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The root of a split tree. This sets up the initial SplitNode state and renders. There is only ever
|
||||
@ -93,18 +136,33 @@ extension Ghostty {
|
||||
ZStack {
|
||||
switch (node) {
|
||||
case .noSplit(let leaf):
|
||||
TerminalSplitLeaf(leaf: leaf, node: $node, requestClose: $requestClose)
|
||||
.onChange(of: requestClose) { value in
|
||||
guard value else { return }
|
||||
guard let onClose = self.onClose else { return }
|
||||
onClose()
|
||||
}
|
||||
TerminalSplitLeaf(
|
||||
leaf: leaf,
|
||||
neighbors: .empty,
|
||||
node: $node,
|
||||
requestClose: $requestClose
|
||||
)
|
||||
.onChange(of: requestClose) { value in
|
||||
guard value else { return }
|
||||
guard let onClose = self.onClose else { return }
|
||||
onClose()
|
||||
}
|
||||
|
||||
case .horizontal(let container):
|
||||
TerminalSplitContainer(direction: .horizontal, node: $node, container: container)
|
||||
TerminalSplitContainer(
|
||||
direction: .horizontal,
|
||||
neighbors: .empty,
|
||||
node: $node,
|
||||
container: container
|
||||
)
|
||||
|
||||
case .vertical(let container):
|
||||
TerminalSplitContainer(direction: .vertical, node: $node, container: container)
|
||||
TerminalSplitContainer(
|
||||
direction: .vertical,
|
||||
neighbors: .empty,
|
||||
node: $node,
|
||||
container: container
|
||||
)
|
||||
}
|
||||
}
|
||||
.navigationTitle(surfaceTitle ?? "Ghostty")
|
||||
@ -116,6 +174,9 @@ extension Ghostty {
|
||||
/// The leaf to draw the surface for.
|
||||
let leaf: SplitNode.Leaf
|
||||
|
||||
/// The neighbors, used for navigation.
|
||||
let neighbors: SplitNode.Neighbors
|
||||
|
||||
/// The SplitNode that the leaf belongs to.
|
||||
@Binding var node: SplitNode
|
||||
|
||||
@ -123,11 +184,14 @@ extension Ghostty {
|
||||
@Binding var requestClose: Bool
|
||||
|
||||
var body: some View {
|
||||
let pub = NotificationCenter.default.publisher(for: Notification.ghosttyNewSplit, object: leaf.surface)
|
||||
let pubClose = NotificationCenter.default.publisher(for: Notification.ghosttyCloseSurface, object: leaf.surface)
|
||||
let center = NotificationCenter.default
|
||||
let pub = center.publisher(for: Notification.ghosttyNewSplit, object: leaf.surface)
|
||||
let pubClose = center.publisher(for: Notification.ghosttyCloseSurface, object: leaf.surface)
|
||||
let pubFocus = center.publisher(for: Notification.ghosttyFocusSplit, object: leaf.surface)
|
||||
SurfaceWrapper(surfaceView: leaf.surface)
|
||||
.onReceive(pub) { onNewSplit(notification: $0) }
|
||||
.onReceive(pubClose) { _ in requestClose = true }
|
||||
.onReceive(pubFocus) { onMoveFocus(notification: $0) }
|
||||
}
|
||||
|
||||
private func onNewSplit(notification: SwiftUI.Notification) {
|
||||
@ -158,22 +222,31 @@ extension Ghostty {
|
||||
node = .vertical(container)
|
||||
}
|
||||
|
||||
// See fixFocus comment, we have to run this whenever split changes.
|
||||
Self.fixFocus(container.bottomRight, previous: node)
|
||||
// See moveFocus comment, we have to run this whenever split changes.
|
||||
Self.moveFocus(container.bottomRight, previous: node)
|
||||
}
|
||||
|
||||
/// This handles the event to move the split focus (i.e. previous/next) from a keyboard event.
|
||||
private func onMoveFocus(notification: SwiftUI.Notification) {
|
||||
// Determine our desired direction
|
||||
guard let directionAny = notification.userInfo?[Notification.SplitDirectionKey] else { return }
|
||||
guard let direction = directionAny as? SplitFocusDirection else { return }
|
||||
guard let next = neighbors.get(direction: direction) else { return }
|
||||
Self.moveFocus(next, previous: node)
|
||||
}
|
||||
|
||||
/// There is a bug I can't figure out where when changing the split state, the terminal view
|
||||
/// will lose focus. There has to be some nice SwiftUI-native way to fix this but I can't
|
||||
/// figure it out so we're going to do this hacky thing to bring focus back to the terminal
|
||||
/// that should have it.
|
||||
fileprivate static func fixFocus(_ target: SplitNode, previous: SplitNode) {
|
||||
fileprivate static func moveFocus(_ target: SplitNode, previous: SplitNode) {
|
||||
let view = target.preferredFocus()
|
||||
|
||||
DispatchQueue.main.async {
|
||||
// If the callback runs before the surface is attached to a view
|
||||
// then the window will be nil. We just reschedule in that case.
|
||||
guard let window = view.window else {
|
||||
self.fixFocus(target, previous: previous)
|
||||
self.moveFocus(target, previous: previous)
|
||||
return
|
||||
}
|
||||
|
||||
@ -194,6 +267,7 @@ extension Ghostty {
|
||||
/// This represents a split view that is in the horizontal or vertical split state.
|
||||
private struct TerminalSplitContainer: View {
|
||||
let direction: SplitViewDirection
|
||||
let neighbors: SplitNode.Neighbors
|
||||
@Binding var node: SplitNode
|
||||
@StateObject var container: SplitNode.Container
|
||||
|
||||
@ -202,23 +276,41 @@ extension Ghostty {
|
||||
|
||||
var body: some View {
|
||||
SplitView(direction, left: {
|
||||
TerminalSplitNested(node: $container.topLeft, requestClose: $closeTopLeft)
|
||||
.onChange(of: closeTopLeft) { value in
|
||||
guard value else { return }
|
||||
|
||||
// When closing the topLeft, our parent becomes the bottomRight.
|
||||
node = container.bottomRight
|
||||
TerminalSplitLeaf.fixFocus(node, previous: container.topLeft)
|
||||
}
|
||||
let neighborKey: WritableKeyPath<SplitNode.Neighbors, SplitNode?> = direction == .horizontal ? \.right : \.bottom
|
||||
|
||||
TerminalSplitNested(
|
||||
node: $container.topLeft,
|
||||
neighbors: neighbors.update([
|
||||
neighborKey: container.bottomRight,
|
||||
\.next: container.bottomRight,
|
||||
]),
|
||||
requestClose: $closeTopLeft
|
||||
)
|
||||
.onChange(of: closeTopLeft) { value in
|
||||
guard value else { return }
|
||||
|
||||
// When closing the topLeft, our parent becomes the bottomRight.
|
||||
node = container.bottomRight
|
||||
TerminalSplitLeaf.moveFocus(node, previous: container.topLeft)
|
||||
}
|
||||
}, right: {
|
||||
TerminalSplitNested(node: $container.bottomRight, requestClose: $closeBottomRight)
|
||||
.onChange(of: closeBottomRight) { value in
|
||||
guard value else { return }
|
||||
|
||||
// When closing the bottomRight, our parent becomes the topLeft.
|
||||
node = container.topLeft
|
||||
TerminalSplitLeaf.fixFocus(node, previous: container.bottomRight)
|
||||
}
|
||||
let neighborKey: WritableKeyPath<SplitNode.Neighbors, SplitNode?> = direction == .horizontal ? \.left : \.top
|
||||
|
||||
TerminalSplitNested(
|
||||
node: $container.bottomRight,
|
||||
neighbors: neighbors.update([
|
||||
neighborKey: container.topLeft,
|
||||
\.previous: container.topLeft,
|
||||
]),
|
||||
requestClose: $closeBottomRight
|
||||
)
|
||||
.onChange(of: closeBottomRight) { value in
|
||||
guard value else { return }
|
||||
|
||||
// When closing the bottomRight, our parent becomes the topLeft.
|
||||
node = container.topLeft
|
||||
TerminalSplitLeaf.moveFocus(node, previous: container.bottomRight)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@ -227,18 +319,34 @@ extension Ghostty {
|
||||
/// requires there be a binding to the parent node.
|
||||
private struct TerminalSplitNested: View {
|
||||
@Binding var node: SplitNode
|
||||
let neighbors: SplitNode.Neighbors
|
||||
@Binding var requestClose: Bool
|
||||
|
||||
var body: some View {
|
||||
switch (node) {
|
||||
case .noSplit(let leaf):
|
||||
TerminalSplitLeaf(leaf: leaf, node: $node, requestClose: $requestClose)
|
||||
TerminalSplitLeaf(
|
||||
leaf: leaf,
|
||||
neighbors: neighbors,
|
||||
node: $node,
|
||||
requestClose: $requestClose
|
||||
)
|
||||
|
||||
case .horizontal(let container):
|
||||
TerminalSplitContainer(direction: .horizontal, node: $node, container: container)
|
||||
TerminalSplitContainer(
|
||||
direction: .horizontal,
|
||||
neighbors: neighbors,
|
||||
node: $node,
|
||||
container: container
|
||||
)
|
||||
|
||||
case .vertical(let container):
|
||||
TerminalSplitContainer(direction: .vertical, node: $node, container: container)
|
||||
TerminalSplitContainer(
|
||||
direction: .vertical,
|
||||
neighbors: neighbors,
|
||||
node: $node,
|
||||
container: container
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,4 +1,77 @@
|
||||
import SwiftUI
|
||||
import GhosttyKit
|
||||
|
||||
struct Ghostty {
|
||||
// All the notifications that will be emitted will be put here.
|
||||
struct Notification {}
|
||||
}
|
||||
|
||||
// MARK: Surface Notifications
|
||||
|
||||
extension Ghostty {
|
||||
/// An enum that is used for the directions that a split focus event can change.
|
||||
enum SplitFocusDirection {
|
||||
case previous, next, top, bottom, left, right
|
||||
|
||||
/// Initialize from a Ghostty API enum.
|
||||
static func from(direction: ghostty_split_focus_direction_e) -> Self? {
|
||||
switch (direction) {
|
||||
case GHOSTTY_SPLIT_FOCUS_PREVIOUS:
|
||||
return .previous
|
||||
|
||||
case GHOSTTY_SPLIT_FOCUS_NEXT:
|
||||
return .next
|
||||
|
||||
case GHOSTTY_SPLIT_FOCUS_TOP:
|
||||
return .top
|
||||
|
||||
case GHOSTTY_SPLIT_FOCUS_BOTTOM:
|
||||
return .bottom
|
||||
|
||||
case GHOSTTY_SPLIT_FOCUS_LEFT:
|
||||
return .left
|
||||
|
||||
case GHOSTTY_SPLIT_FOCUS_RIGHT:
|
||||
return .right
|
||||
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func toNative() -> ghostty_split_focus_direction_e {
|
||||
switch (self) {
|
||||
case .previous:
|
||||
return GHOSTTY_SPLIT_FOCUS_PREVIOUS
|
||||
|
||||
case .next:
|
||||
return GHOSTTY_SPLIT_FOCUS_NEXT
|
||||
|
||||
case .top:
|
||||
return GHOSTTY_SPLIT_FOCUS_TOP
|
||||
|
||||
case .bottom:
|
||||
return GHOSTTY_SPLIT_FOCUS_BOTTOM
|
||||
|
||||
case .left:
|
||||
return GHOSTTY_SPLIT_FOCUS_LEFT
|
||||
|
||||
case .right:
|
||||
return GHOSTTY_SPLIT_FOCUS_RIGHT
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
extension Ghostty.Notification {
|
||||
/// Posted when a new split is requested. The sending object will be the surface that had focus. The
|
||||
/// userdata has one key "direction" with the direction to split to.
|
||||
static let ghosttyNewSplit = Notification.Name("com.mitchellh.ghostty.newSplit")
|
||||
|
||||
/// Close the calling surface.
|
||||
static let ghosttyCloseSurface = Notification.Name("com.mitchellh.ghostty.closeSurface")
|
||||
|
||||
/// Focus previous/next split. Has a SplitFocusDirection in the userinfo.
|
||||
static let ghosttyFocusSplit = Notification.Name("com.mitchellh.ghostty.focusSplit")
|
||||
static let SplitDirectionKey = ghosttyFocusSplit.rawValue
|
||||
}
|
||||
|
@ -509,18 +509,6 @@ extension Ghostty {
|
||||
0x4E: GHOSTTY_KEY_KP_SUBTRACT,
|
||||
];
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: Surface Notifications
|
||||
|
||||
extension Ghostty.Notification {
|
||||
/// Posted when a new split is requested. The sending object will be the surface that had focus. The
|
||||
/// userdata has one key "direction" with the direction to split to.
|
||||
static let ghosttyNewSplit = Notification.Name("com.mitchellh.ghostty.newSplit")
|
||||
|
||||
/// Close the calling surface.
|
||||
static let ghosttyCloseSurface = Notification.Name("com.mitchellh.ghostty.closeSurface")
|
||||
}
|
||||
|
||||
// MARK: Surface Environment Keys
|
||||
|
@ -37,6 +37,26 @@ struct GhosttyApp: App {
|
||||
Button("Close", action: close).keyboardShortcut("w", modifiers: [.command])
|
||||
Button("Close Window", action: Self.closeWindow).keyboardShortcut("w", modifiers: [.command, .shift])
|
||||
}
|
||||
|
||||
CommandGroup(before: .windowArrangement) {
|
||||
Divider()
|
||||
Button("Select Previous Split") { splitMoveFocus(direction: .previous) }
|
||||
.keyboardShortcut("[", modifiers: .command)
|
||||
Button("Select Next Split") { splitMoveFocus(direction: .next) }
|
||||
.keyboardShortcut("]", modifiers: .command)
|
||||
Menu("Select Split") {
|
||||
Button("Select Split Above") { splitMoveFocus(direction: .top) }
|
||||
.keyboardShortcut(.upArrow, modifiers: [.command, .option])
|
||||
Button("Select Split Below") { splitMoveFocus(direction: .bottom) }
|
||||
.keyboardShortcut(.downArrow, modifiers: [.command, .option])
|
||||
Button("Select Split Left") { splitMoveFocus(direction: .left) }
|
||||
.keyboardShortcut(.leftArrow, modifiers: [.command, .option])
|
||||
Button("Select Split Right") { splitMoveFocus(direction: .right)}
|
||||
.keyboardShortcut(.rightArrow, modifiers: [.command, .option])
|
||||
}
|
||||
|
||||
Divider()
|
||||
}
|
||||
}
|
||||
|
||||
Settings {
|
||||
@ -76,6 +96,12 @@ struct GhosttyApp: App {
|
||||
guard let surface = surfaceView.surface else { return }
|
||||
ghostty.split(surface: surface, direction: GHOSTTY_SPLIT_DOWN)
|
||||
}
|
||||
|
||||
func splitMoveFocus(direction: Ghostty.SplitFocusDirection) {
|
||||
guard let surfaceView = focusedSurface else { return }
|
||||
guard let surface = surfaceView.surface else { return }
|
||||
ghostty.splitMoveFocus(surface: surface, direction: direction)
|
||||
}
|
||||
}
|
||||
|
||||
class AppDelegate: NSObject, NSApplicationDelegate {
|
||||
|
@ -947,6 +947,12 @@ pub fn keyCallback(
|
||||
} else log.warn("runtime doesn't implement newSplit", .{});
|
||||
},
|
||||
|
||||
.goto_split => |direction| {
|
||||
if (@hasDecl(apprt.Surface, "gotoSplit")) {
|
||||
self.rt_surface.gotoSplit(direction);
|
||||
} else log.warn("runtime doesn't implement gotoSplit", .{});
|
||||
},
|
||||
|
||||
.close_surface => {
|
||||
if (@hasDecl(apprt.Surface, "closeSurface")) {
|
||||
try self.rt_surface.closeSurface();
|
||||
|
@ -51,6 +51,9 @@ pub const App = struct {
|
||||
|
||||
/// Close the current surface given by this function.
|
||||
close_surface: ?*const fn (SurfaceUD) callconv(.C) void = null,
|
||||
|
||||
/// Focus the previous/next split (if any).
|
||||
focus_split: ?*const fn (SurfaceUD, input.SplitFocusDirection) callconv(.C) void = null,
|
||||
};
|
||||
|
||||
core_app: *CoreApp,
|
||||
@ -166,13 +169,22 @@ pub const Surface = struct {
|
||||
|
||||
pub fn closeSurface(self: *const Surface) !void {
|
||||
const func = self.app.opts.close_surface orelse {
|
||||
log.info("runtime embedder does not closing a surface", .{});
|
||||
log.info("runtime embedder does not support closing a surface", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
func(self.opts.userdata);
|
||||
}
|
||||
|
||||
pub fn gotoSplit(self: *const Surface, direction: input.SplitFocusDirection) void {
|
||||
const func = self.app.opts.focus_split orelse {
|
||||
log.info("runtime embedder does not support focus split", .{});
|
||||
return;
|
||||
};
|
||||
|
||||
func(self.opts.userdata, direction);
|
||||
}
|
||||
|
||||
pub fn getContentScale(self: *const Surface) !apprt.ContentScale {
|
||||
return self.content_scale;
|
||||
}
|
||||
@ -481,4 +493,9 @@ pub const CAPI = struct {
|
||||
export fn ghostty_surface_split(ptr: *Surface, direction: input.SplitDirection) void {
|
||||
ptr.newSplit(direction) catch {};
|
||||
}
|
||||
|
||||
/// Focus on the next split (if any).
|
||||
export fn ghostty_surface_split_focus(ptr: *Surface, direction: input.SplitFocusDirection) void {
|
||||
ptr.gotoSplit(direction);
|
||||
}
|
||||
};
|
||||
|
@ -311,6 +311,36 @@ pub const Config = struct {
|
||||
.{ .key = .d, .mods = .{ .super = true, .shift = true } },
|
||||
.{ .new_split = .down },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .left_bracket, .mods = .{ .super = true } },
|
||||
.{ .goto_split = .previous },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .right_bracket, .mods = .{ .super = true } },
|
||||
.{ .goto_split = .next },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .up, .mods = .{ .super = true, .alt = true } },
|
||||
.{ .goto_split = .top },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .down, .mods = .{ .super = true, .alt = true } },
|
||||
.{ .goto_split = .bottom },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .left, .mods = .{ .super = true, .alt = true } },
|
||||
.{ .goto_split = .left },
|
||||
);
|
||||
try result.keybind.set.put(
|
||||
alloc,
|
||||
.{ .key = .right, .mods = .{ .super = true, .alt = true } },
|
||||
.{ .goto_split = .right },
|
||||
);
|
||||
{
|
||||
// Cmd+N for goto tab N
|
||||
const start = @enumToInt(inputpkg.Key.one);
|
||||
|
@ -4,6 +4,7 @@ pub usingnamespace @import("input/mouse.zig");
|
||||
pub usingnamespace @import("input/key.zig");
|
||||
pub const Binding = @import("input/Binding.zig");
|
||||
pub const SplitDirection = Binding.Action.SplitDirection;
|
||||
pub const SplitFocusDirection = Binding.Action.SplitFocusDirection;
|
||||
|
||||
test {
|
||||
std.testing.refAllDecls(@This());
|
||||
|
@ -184,6 +184,9 @@ pub const Action = union(enum) {
|
||||
/// in the direction given.
|
||||
new_split: SplitDirection,
|
||||
|
||||
/// Focus on a split in a given direction.
|
||||
goto_split: SplitFocusDirection,
|
||||
|
||||
/// Close the current "surface", whether that is a window, tab, split,
|
||||
/// etc. This only closes ONE surface.
|
||||
close_surface: void,
|
||||
@ -207,6 +210,17 @@ pub const Action = union(enum) {
|
||||
|
||||
// Note: we don't support top or left yet
|
||||
};
|
||||
|
||||
// Extern because it is used in the embedded runtime ABI.
|
||||
pub const SplitFocusDirection = enum(c_int) {
|
||||
previous,
|
||||
next,
|
||||
|
||||
top,
|
||||
left,
|
||||
bottom,
|
||||
right,
|
||||
};
|
||||
};
|
||||
|
||||
/// Trigger is the associated key state that can trigger an action.
|
||||
|
Reference in New Issue
Block a user