mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-07-16 08:46:08 +03:00
Merge pull request #169 from mitchellh/mrn/goto-tab-macos
macOS: implement cmd+[0-9] to goto tab
This commit is contained in:
@ -238,6 +238,7 @@ 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_new_split_cb)(void *, ghostty_split_direction_e);
|
||||||
typedef void (*ghostty_runtime_close_surface_cb)(void *, bool);
|
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_focus_split_cb)(void *, ghostty_split_focus_direction_e);
|
||||||
|
typedef void (*ghostty_runtime_goto_tab_cb)(void *, int32_t);
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
void *userdata;
|
void *userdata;
|
||||||
@ -249,6 +250,7 @@ typedef struct {
|
|||||||
ghostty_runtime_new_split_cb new_split_cb;
|
ghostty_runtime_new_split_cb new_split_cb;
|
||||||
ghostty_runtime_close_surface_cb close_surface_cb;
|
ghostty_runtime_close_surface_cb close_surface_cb;
|
||||||
ghostty_runtime_focus_split_cb focus_split_cb;
|
ghostty_runtime_focus_split_cb focus_split_cb;
|
||||||
|
ghostty_runtime_goto_tab_cb goto_tab_cb;
|
||||||
} ghostty_runtime_config_s;
|
} ghostty_runtime_config_s;
|
||||||
|
|
||||||
//-------------------------------------------------------------------
|
//-------------------------------------------------------------------
|
||||||
|
@ -26,6 +26,9 @@ struct ContentView: View {
|
|||||||
NSApplication.shared.reply(toApplicationShouldTerminate: true)
|
NSApplication.shared.reply(toApplicationShouldTerminate: true)
|
||||||
}
|
}
|
||||||
case .ready:
|
case .ready:
|
||||||
|
let center = NotificationCenter.default
|
||||||
|
let gotoTab = center.publisher(for: Ghostty.Notification.ghosttyGotoTab)
|
||||||
|
|
||||||
let confirmQuitting = Binding<Bool>(get: {
|
let confirmQuitting = Binding<Bool>(get: {
|
||||||
self.appDelegate.confirmQuit && (self.window?.isKeyWindow ?? false)
|
self.appDelegate.confirmQuit && (self.window?.isKeyWindow ?? false)
|
||||||
}, set: {
|
}, set: {
|
||||||
@ -35,6 +38,7 @@ struct ContentView: View {
|
|||||||
Ghostty.TerminalSplit(onClose: Self.closeWindow)
|
Ghostty.TerminalSplit(onClose: Self.closeWindow)
|
||||||
.ghosttyApp(ghostty.app!)
|
.ghosttyApp(ghostty.app!)
|
||||||
.background(WindowAccessor(window: $window))
|
.background(WindowAccessor(window: $window))
|
||||||
|
.onReceive(gotoTab) { onGotoTab(notification: $0) }
|
||||||
.confirmationDialog(
|
.confirmationDialog(
|
||||||
"Quit Ghostty?",
|
"Quit Ghostty?",
|
||||||
isPresented: confirmQuitting) {
|
isPresented: confirmQuitting) {
|
||||||
@ -57,4 +61,27 @@ struct ContentView: View {
|
|||||||
guard let currentWindow = NSApp.keyWindow else { return }
|
guard let currentWindow = NSApp.keyWindow else { return }
|
||||||
currentWindow.close()
|
currentWindow.close()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func onGotoTab(notification: SwiftUI.Notification) {
|
||||||
|
// Notification center indiscriminately sends to every subscriber (makes sense)
|
||||||
|
// but we only want to process this once. In order to process it once lets only
|
||||||
|
// handle it if we're the focused window.
|
||||||
|
guard let window = self.window else { return }
|
||||||
|
guard window.isKeyWindow else { return }
|
||||||
|
|
||||||
|
// Get the tab index from the notification
|
||||||
|
guard let tabIndexAny = notification.userInfo?[Ghostty.Notification.GotoTabKey] else { return }
|
||||||
|
guard let tabIndex = tabIndexAny as? Int32 else { return }
|
||||||
|
|
||||||
|
guard let windowController = window.windowController else { return }
|
||||||
|
guard let tabGroup = windowController.window?.tabGroup else { return }
|
||||||
|
let tabbedWindows = tabGroup.windows
|
||||||
|
|
||||||
|
// Tabs are 0-indexed here, so we subtract one from the key the user hit.
|
||||||
|
let adjustedIndex = Int(tabIndex - 1);
|
||||||
|
guard adjustedIndex >= 0 && adjustedIndex < tabbedWindows.count else { return }
|
||||||
|
|
||||||
|
let targetWindow = tabbedWindows[adjustedIndex]
|
||||||
|
targetWindow.makeKeyAndOrderFront(nil)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -61,7 +61,8 @@ extension Ghostty {
|
|||||||
write_clipboard_cb: { userdata, str in AppState.writeClipboard(userdata, string: str) },
|
write_clipboard_cb: { userdata, str in AppState.writeClipboard(userdata, string: str) },
|
||||||
new_split_cb: { userdata, direction in AppState.newSplit(userdata, direction: direction) },
|
new_split_cb: { userdata, direction in AppState.newSplit(userdata, direction: direction) },
|
||||||
close_surface_cb: { userdata, processAlive in AppState.closeSurface(userdata, processAlive: processAlive) },
|
close_surface_cb: { userdata, processAlive in AppState.closeSurface(userdata, processAlive: processAlive) },
|
||||||
focus_split_cb: { userdata, direction in AppState.focusSplit(userdata, direction: direction) }
|
focus_split_cb: { userdata, direction in AppState.focusSplit(userdata, direction: direction) },
|
||||||
|
goto_tab_cb: { userdata, n in AppState.gotoTab(userdata, n: n) }
|
||||||
)
|
)
|
||||||
|
|
||||||
// Create the ghostty app.
|
// Create the ghostty app.
|
||||||
@ -157,6 +158,17 @@ extension Ghostty {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
static func gotoTab(_ userdata: UnsafeMutableRawPointer?, n: Int32) {
|
||||||
|
guard let surface = self.surfaceUserdata(from: userdata) else { return }
|
||||||
|
NotificationCenter.default.post(
|
||||||
|
name: Notification.ghosttyGotoTab,
|
||||||
|
object: surface,
|
||||||
|
userInfo: [
|
||||||
|
Notification.GotoTabKey: n,
|
||||||
|
]
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
static func readClipboard(_ userdata: UnsafeMutableRawPointer?) -> UnsafePointer<CChar>? {
|
static func readClipboard(_ userdata: UnsafeMutableRawPointer?) -> UnsafePointer<CChar>? {
|
||||||
guard let appState = self.appState(fromSurface: userdata) else { return nil }
|
guard let appState = self.appState(fromSurface: userdata) else { return nil }
|
||||||
guard let str = NSPasteboard.general.string(forType: .string) else { return nil }
|
guard let str = NSPasteboard.general.string(forType: .string) else { return nil }
|
||||||
|
@ -74,6 +74,10 @@ extension Ghostty.Notification {
|
|||||||
/// Focus previous/next split. Has a SplitFocusDirection in the userinfo.
|
/// Focus previous/next split. Has a SplitFocusDirection in the userinfo.
|
||||||
static let ghosttyFocusSplit = Notification.Name("com.mitchellh.ghostty.focusSplit")
|
static let ghosttyFocusSplit = Notification.Name("com.mitchellh.ghostty.focusSplit")
|
||||||
static let SplitDirectionKey = ghosttyFocusSplit.rawValue
|
static let SplitDirectionKey = ghosttyFocusSplit.rawValue
|
||||||
|
|
||||||
|
/// Goto tab. Has tab index in the userinfo.
|
||||||
|
static let ghosttyGotoTab = Notification.Name("com.mitchellh.ghostty.gotoTab")
|
||||||
|
static let GotoTabKey = ghosttyGotoTab.rawValue
|
||||||
}
|
}
|
||||||
|
|
||||||
// Make the input enum hashable.
|
// Make the input enum hashable.
|
||||||
|
@ -20,7 +20,9 @@ struct GhosttyApp: App {
|
|||||||
WindowGroup {
|
WindowGroup {
|
||||||
ContentView(ghostty: ghostty)
|
ContentView(ghostty: ghostty)
|
||||||
}
|
}
|
||||||
|
|
||||||
.backport.defaultSize(width: 800, height: 600)
|
.backport.defaultSize(width: 800, height: 600)
|
||||||
|
|
||||||
.commands {
|
.commands {
|
||||||
CommandGroup(after: .newItem) {
|
CommandGroup(after: .newItem) {
|
||||||
Button("New Tab", action: Self.newTab).keyboardShortcut("t", modifiers: [.command])
|
Button("New Tab", action: Self.newTab).keyboardShortcut("t", modifiers: [.command])
|
||||||
|
@ -61,6 +61,9 @@ pub const App = struct {
|
|||||||
|
|
||||||
/// Focus the previous/next split (if any).
|
/// Focus the previous/next split (if any).
|
||||||
focus_split: ?*const fn (SurfaceUD, input.SplitFocusDirection) callconv(.C) void = null,
|
focus_split: ?*const fn (SurfaceUD, input.SplitFocusDirection) callconv(.C) void = null,
|
||||||
|
|
||||||
|
/// Goto tab
|
||||||
|
goto_tab: ?*const fn (SurfaceUD, usize) callconv(.C) void = null,
|
||||||
};
|
};
|
||||||
|
|
||||||
core_app: *CoreApp,
|
core_app: *CoreApp,
|
||||||
@ -359,6 +362,15 @@ pub const Surface = struct {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn gotoTab(self: *Surface, n: usize) void {
|
||||||
|
const func = self.app.opts.goto_tab orelse {
|
||||||
|
log.info("runtime embedder does not goto_tab", .{});
|
||||||
|
return;
|
||||||
|
};
|
||||||
|
|
||||||
|
func(self.opts.userdata, n);
|
||||||
|
}
|
||||||
|
|
||||||
/// The cursor position from the host directly is in screen coordinates but
|
/// The cursor position from the host directly is in screen coordinates but
|
||||||
/// all our interface works in pixels.
|
/// all our interface works in pixels.
|
||||||
fn cursorPosToPixels(self: *const Surface, pos: apprt.CursorPos) !apprt.CursorPos {
|
fn cursorPosToPixels(self: *const Surface, pos: apprt.CursorPos) !apprt.CursorPos {
|
||||||
|
Reference in New Issue
Block a user