macos: make move_tab work

This commit is contained in:
Mitchell Hashimoto
2024-10-25 11:53:45 -07:00
parent 520dda65cb
commit de5ec5d83e
8 changed files with 117 additions and 5 deletions

View File

@ -15,6 +15,7 @@ extern "C" {
#include <stdbool.h>
#include <stddef.h>
#include <stdint.h>
#include <sys/types.h>
//-------------------------------------------------------------------
// Macros
@ -379,6 +380,11 @@ typedef struct {
ghostty_action_resize_split_direction_e direction;
} ghostty_action_resize_split_s;
// apprt.action.MoveTab
typedef struct {
ssize_t amount;
} ghostty_action_move_tab_s;
// apprt.action.GotoTab
typedef enum {
GHOSTTY_GOTO_TAB_PREVIOUS = -1,
@ -517,6 +523,7 @@ typedef enum {
GHOSTTY_ACTION_TOGGLE_WINDOW_DECORATIONS,
GHOSTTY_ACTION_TOGGLE_QUICK_TERMINAL,
GHOSTTY_ACTION_TOGGLE_VISIBILITY,
GHOSTTY_ACTION_MOVE_TAB,
GHOSTTY_ACTION_GOTO_TAB,
GHOSTTY_ACTION_GOTO_SPLIT,
GHOSTTY_ACTION_RESIZE_SPLIT,
@ -543,6 +550,7 @@ typedef enum {
typedef union {
ghostty_action_split_direction_e new_split;
ghostty_action_fullscreen_e toggle_fullscreen;
ghostty_action_move_tab_s move_tab;
ghostty_action_goto_tab_e goto_tab;
ghostty_action_goto_split_e goto_split;
ghostty_action_resize_split_s resize_split;

View File

@ -32,6 +32,7 @@
A5333E242B5A22D9008AEFF7 /* Ghostty.Shell.swift in Sources */ = {isa = PBXBuildFile; fileRef = A56D58852ACDDB4100508D2C /* Ghostty.Shell.swift */; };
A53426352A7DA53D00EBB7A2 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53426342A7DA53D00EBB7A2 /* AppDelegate.swift */; };
A535B9DA299C569B0017E2E4 /* ErrorView.swift in Sources */ = {isa = PBXBuildFile; fileRef = A535B9D9299C569B0017E2E4 /* ErrorView.swift */; };
A53A6C032CCC1B7F00943E98 /* Ghostty.Action.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53A6C022CCC1B7D00943E98 /* Ghostty.Action.swift */; };
A53D0C8E2B53B0EA00305CE6 /* GhosttyKit.xcframework in Frameworks */ = {isa = PBXBuildFile; fileRef = A5D495A1299BEC7E00DD1313 /* GhosttyKit.xcframework */; };
A53D0C942B53B43700305CE6 /* iOSApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = A53D0C932B53B43700305CE6 /* iOSApp.swift */; };
A53D0C952B53B4D800305CE6 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = A5B30538299BEAAB0047F10C /* Assets.xcassets */; };
@ -115,6 +116,7 @@
A5333E212B5A2128008AEFF7 /* SurfaceView_AppKit.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SurfaceView_AppKit.swift; sourceTree = "<group>"; };
A53426342A7DA53D00EBB7A2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = "<group>"; };
A535B9D9299C569B0017E2E4 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = "<group>"; };
A53A6C022CCC1B7D00943E98 /* Ghostty.Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Action.swift; sourceTree = "<group>"; };
A53D0C932B53B43700305CE6 /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = "<group>"; };
A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.App.swift; sourceTree = "<group>"; };
A54D786B2CA79788001B19B1 /* BaseTerminalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTerminalController.swift; sourceTree = "<group>"; };
@ -317,6 +319,7 @@
A59FB5CE2AE0DB50009128F3 /* InspectorView.swift */,
A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */,
A514C8D52B54A16400493A16 /* Ghostty.Config.swift */,
A53A6C022CCC1B7D00943E98 /* Ghostty.Action.swift */,
A5278A9A2AA05B2600CD3039 /* Ghostty.Input.swift */,
A56D58852ACDDB4100508D2C /* Ghostty.Shell.swift */,
A59630A32AF059BB00D64628 /* Ghostty.SplitNode.swift */,
@ -601,6 +604,7 @@
A57D79272C9C879B001D522E /* SecureInput.swift in Sources */,
A5CEAFDC29B8009000646FDA /* SplitView.swift in Sources */,
A5CDF1932AAF9E0800513312 /* ConfigurationErrorsController.swift in Sources */,
A53A6C032CCC1B7F00943E98 /* Ghostty.Action.swift in Sources */,
A59FB5D12AE0DEA7009128F3 /* MetalView.swift in Sources */,
A55685E029A03A9F004303CE /* AppError.swift in Sources */,
A52FFF572CA90484000C6A5B /* QuickTerminalScreen.swift in Sources */,

View File

@ -40,6 +40,11 @@ class TerminalController: BaseTerminalController {
selector: #selector(onToggleFullscreen),
name: Ghostty.Notification.ghosttyToggleFullscreen,
object: nil)
center.addObserver(
self,
selector: #selector(onMoveTab),
name: .ghosttyMoveTab,
object: nil)
center.addObserver(
self,
selector: #selector(onGotoTab),
@ -474,6 +479,44 @@ class TerminalController: BaseTerminalController {
//MARK: - Notifications
@objc private func onMoveTab(notification: SwiftUI.Notification) {
guard let target = notification.object as? Ghostty.SurfaceView else { return }
guard target == self.focusedSurface else { return }
guard let window = self.window else { return }
// Get the move action
guard let action = notification.userInfo?[Notification.Name.GhosttyMoveTabKey] as? Ghostty.Action.MoveTab else { return }
guard action.amount != 0 else { return }
// Determine our current selected index
guard let windowController = window.windowController else { return }
guard let tabGroup = windowController.window?.tabGroup else { return }
guard let selectedWindow = tabGroup.selectedWindow else { return }
let tabbedWindows = tabGroup.windows
guard tabbedWindows.count > 0 else { return }
guard let selectedIndex = tabbedWindows.firstIndex(where: { $0 == selectedWindow }) else { return }
// Determine the final index we want to insert our tab
let finalIndex: Int
if action.amount < 0 {
finalIndex = selectedIndex - min(selectedIndex, -action.amount)
} else {
let remaining: Int = tabbedWindows.count - 1 - selectedIndex
finalIndex = selectedIndex + min(remaining, action.amount)
}
// If our index is the same we do nothing
guard finalIndex != selectedIndex else { return }
// Get our parent
let parent = tabbedWindows[finalIndex]
// Move our current selected window to the proper index
tabGroup.removeWindow(selectedWindow)
parent.addTabbedWindow(selectedWindow, ordered: action.amount < 0 ? .below : .above)
selectedWindow.makeKeyAndOrderFront(nil)
}
@objc private func onGotoTab(notification: SwiftUI.Notification) {
guard let target = notification.object as? Ghostty.SurfaceView else { return }
guard target == self.focusedSurface else { return }

View File

@ -0,0 +1,15 @@
import GhosttyKit
extension Ghostty {
struct Action {}
}
extension Ghostty.Action {
struct MoveTab {
let amount: Int
init(c: ghostty_action_move_tab_s) {
self.amount = c.amount
}
}
}

View File

@ -458,6 +458,9 @@ extension Ghostty {
case GHOSTTY_ACTION_TOGGLE_FULLSCREEN:
toggleFullscreen(app, target: target, mode: action.action.toggle_fullscreen)
case GHOSTTY_ACTION_MOVE_TAB:
moveTab(app, target: target, move: action.action.move_tab)
case GHOSTTY_ACTION_GOTO_TAB:
gotoTab(app, target: target, tab: action.action.goto_tab)
@ -666,6 +669,31 @@ extension Ghostty {
appDelegate.toggleVisibility(self)
}
private static func moveTab(
_ app: ghostty_app_t,
target: ghostty_target_s,
move: ghostty_action_move_tab_s) {
switch (target.tag) {
case GHOSTTY_TARGET_APP:
Ghostty.logger.warning("move tab does nothing with an app target")
return
case GHOSTTY_TARGET_SURFACE:
guard let surface = target.target.surface else { return }
guard let surfaceView = self.surfaceView(from: surface) else { return }
NotificationCenter.default.post(
name: .ghosttyMoveTab,
object: surfaceView,
userInfo: [
SwiftUI.Notification.Name.GhosttyMoveTabKey: Action.MoveTab(c: move),
]
)
default:
assertionFailure()
}
}
private static func gotoTab(
_ app: ghostty_app_t,
target: ghostty_target_s,

View File

@ -196,8 +196,16 @@ extension Ghostty {
}
}
// MARK: Surface Notifications
// MARK: Surface Notification
extension Notification.Name {
/// Goto tab. Has tab index in the userinfo.
static let ghosttyMoveTab = Notification.Name("com.mitchellh.ghostty.moveTab")
static let GhosttyMoveTabKey = ghosttyMoveTab.rawValue
}
// NOTE: I am moving all of these to Notification.Name extensions over time. This
// namespace was the old namespace.
extension Ghostty.Notification {
/// Used to pass a configuration along when creating a new tab/window/split.
static let NewSurfaceConfigKey = "com.mitchellh.ghostty.newSurfaceConfig"

View File

@ -3916,7 +3916,7 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
.move_tab => |position| try self.rt_app.performAction(
.{ .surface = self },
.move_tab,
position,
.{ .amount = position },
),
.new_split => |direction| try self.rt_app.performAction(

View File

@ -101,9 +101,11 @@ pub const Action = union(Key) {
toggle_visibility,
/// Moves a tab by a relative offset.
/// Adjusts the tab position based on `offset` (e.g., -1 for left, +1 for right).
/// If the new position is out of bounds, it wraps around cyclically within the tab range.
move_tab: isize,
///
/// Adjusts the tab position based on `offset` (e.g., -1 for left, +1
/// for right). If the new position is out of bounds, it wraps around
/// cyclically within the tab range.
move_tab: MoveTab,
/// Jump to a specific tab. Must handle the scenario that the tab
/// value is invalid.
@ -314,6 +316,10 @@ pub const ResizeSplit = extern struct {
};
};
pub const MoveTab = extern struct {
amount: isize,
};
/// The tab to jump to. This is non-exhaustive so that integer values represent
/// the index (zero-based) of the tab to jump to. Negative values are special
/// values.