Merge pull request #169 from mitchellh/mrn/goto-tab-macos

macOS: implement cmd+[0-9] to goto tab
This commit is contained in:
Mitchell Hashimoto
2023-07-01 09:21:33 -07:00
committed by GitHub
6 changed files with 60 additions and 1 deletions

View File

@ -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_close_surface_cb)(void *, bool);
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 {
void *userdata;
@ -249,6 +250,7 @@ typedef struct {
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_goto_tab_cb goto_tab_cb;
} ghostty_runtime_config_s;
//-------------------------------------------------------------------

View File

@ -26,6 +26,9 @@ struct ContentView: View {
NSApplication.shared.reply(toApplicationShouldTerminate: true)
}
case .ready:
let center = NotificationCenter.default
let gotoTab = center.publisher(for: Ghostty.Notification.ghosttyGotoTab)
let confirmQuitting = Binding<Bool>(get: {
self.appDelegate.confirmQuit && (self.window?.isKeyWindow ?? false)
}, set: {
@ -35,6 +38,7 @@ struct ContentView: View {
Ghostty.TerminalSplit(onClose: Self.closeWindow)
.ghosttyApp(ghostty.app!)
.background(WindowAccessor(window: $window))
.onReceive(gotoTab) { onGotoTab(notification: $0) }
.confirmationDialog(
"Quit Ghostty?",
isPresented: confirmQuitting) {
@ -57,4 +61,27 @@ struct ContentView: View {
guard let currentWindow = NSApp.keyWindow else { return }
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)
}
}

View File

@ -61,7 +61,8 @@ extension Ghostty {
write_clipboard_cb: { userdata, str in AppState.writeClipboard(userdata, string: str) },
new_split_cb: { userdata, direction in AppState.newSplit(userdata, direction: direction) },
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.
@ -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>? {
guard let appState = self.appState(fromSurface: userdata) else { return nil }
guard let str = NSPasteboard.general.string(forType: .string) else { return nil }

View File

@ -74,6 +74,10 @@ extension Ghostty.Notification {
/// Focus previous/next split. Has a SplitFocusDirection in the userinfo.
static let ghosttyFocusSplit = Notification.Name("com.mitchellh.ghostty.focusSplit")
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.

View File

@ -20,7 +20,9 @@ struct GhosttyApp: App {
WindowGroup {
ContentView(ghostty: ghostty)
}
.backport.defaultSize(width: 800, height: 600)
.commands {
CommandGroup(after: .newItem) {
Button("New Tab", action: Self.newTab).keyboardShortcut("t", modifiers: [.command])

View File

@ -61,6 +61,9 @@ pub const App = struct {
/// Focus the previous/next split (if any).
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,
@ -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
/// all our interface works in pixels.
fn cursorPosToPixels(self: *const Surface, pos: apprt.CursorPos) !apprt.CursorPos {