From 465d60def86c86593eb28d9d4b39aa4a06452326 Mon Sep 17 00:00:00 2001 From: axdank Date: Thu, 24 Oct 2024 00:01:54 -0300 Subject: [PATCH 1/5] gui: add move_current_tab action --- src/Surface.zig | 6 ++++++ src/apprt/action.zig | 10 ++++++++++ src/apprt/glfw.zig | 1 + src/apprt/gtk/App.zig | 18 ++++++++++++++++++ src/apprt/gtk/Window.zig | 9 +++++++++ src/apprt/gtk/notebook.zig | 27 +++++++++++++++++++++++++++ src/input/Binding.zig | 4 ++++ 7 files changed, 75 insertions(+) diff --git a/src/Surface.zig b/src/Surface.zig index bd5073e3a..dca9c6316 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3913,6 +3913,12 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool }, ), + .move_current_tab => |position| try self.rt_app.performAction( + .{ .surface = self }, + .move_current_tab, + position, + ), + .new_split => |direction| try self.rt_app.performAction( .{ .surface = self }, .new_split, diff --git a/src/apprt/action.zig b/src/apprt/action.zig index 9fce8502f..e9c1f1005 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -100,6 +100,9 @@ pub const Action = union(Key) { /// Toggle the visibility of all Ghostty terminal windows. toggle_visibility, + /// Move current tab given a position + move_current_tab: isize, + /// Jump to a specific tab. Must handle the scenario that the tab /// value is invalid. goto_tab: GotoTab, @@ -190,6 +193,7 @@ pub const Action = union(Key) { toggle_window_decorations, toggle_quick_terminal, toggle_visibility, + move_current_tab, goto_tab, goto_split, resize_split, @@ -318,6 +322,12 @@ pub const GotoTab = enum(c_int) { _, }; +/// Move current tab . +pub const MoveCurrentTabDirection = enum(c_int) { + right, + left, +}; + /// The fullscreen mode to toggle to if we're moving to fullscreen. pub const Fullscreen = enum(c_int) { native, diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index 668dd9143..49eecab32 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -213,6 +213,7 @@ pub const App = struct { .toggle_quick_terminal, .toggle_visibility, .goto_tab, + .move_current_tab, .inspector, .render_inspector, .quit_timer, diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 10a2bdc02..595e8e5b6 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -456,6 +456,7 @@ pub fn performAction( .new_tab => try self.newTab(target), .goto_tab => self.gotoTab(target, value), + .move_current_tab => self.moveCurrentTab(target, value), .new_split => try self.newSplit(target, value), .resize_split => self.resizeSplit(target, value), .equalize_splits => self.equalizeSplits(target), @@ -527,6 +528,23 @@ fn gotoTab(_: *App, target: apprt.Target, tab: apprt.action.GotoTab) void { } } +fn moveCurrentTab(_: *App, target: apprt.Target, position: isize) void { + switch (target) { + .app => {}, + .surface => |v| { + const window = v.rt_surface.container.window() orelse { + log.info( + "moveCurrentTab invalid for container={s}", + .{@tagName(v.rt_surface.container)}, + ); + return; + }; + + window.moveCurrentTab(v.rt_surface, @intCast(position)); + }, + } +} + fn newSplit( self: *App, target: apprt.Target, diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index 65041d600..8fb6cac77 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -456,6 +456,15 @@ pub fn gotoNextTab(self: *Window, surface: *Surface) void { self.focusCurrentTab(); } +/// Move the current tab for a surface. +pub fn moveCurrentTab(self: *Window, surface: *Surface, position: c_int) void { + const tab = surface.container.tab() orelse { + log.info("surface is not attached to a tab bar, cannot navigate", .{}); + return; + }; + self.notebook.moveTab(tab, position); +} + /// Go to the next tab for a surface. pub fn gotoLastTab(self: *Window) void { const max = self.notebook.nPages() -| 1; diff --git a/src/apprt/gtk/notebook.zig b/src/apprt/gtk/notebook.zig index 53cadc2d2..dce729989 100644 --- a/src/apprt/gtk/notebook.zig +++ b/src/apprt/gtk/notebook.zig @@ -183,6 +183,33 @@ pub const Notebook = union(enum) { self.gotoNthTab(next_idx); } + pub fn moveTab(self: Notebook, tab: *Tab, position: c_int) void { + switch (self) { + .gtk_notebook => |notebook| { + c.gtk_notebook_reorder_child(notebook, @ptrCast(tab.box), position); + }, + .adw_tab_view => |tab_view| { + if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable; + const page = c.adw_tab_view_get_page(tab_view, @ptrCast(tab.box)); + + const page_idx = self.getTabPosition(tab) orelse return; + + const max = self.nPages() -| 1; + var new_position: c_int = page_idx + position; + + if (new_position < 0) { + new_position = max; + } else if (new_position > max) { + new_position = 0; + } + + if (new_position == page_idx) return; + + _ = c.adw_tab_view_reorder_page(tab_view, page, new_position); + }, + } + } + pub fn setTabLabel(self: Notebook, tab: *Tab, title: [:0]const u8) void { switch (self) { .adw_tab_view => |tab_view| { diff --git a/src/input/Binding.zig b/src/input/Binding.zig index c9e90d946..943a704c4 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -300,6 +300,9 @@ pub const Action = union(enum) { /// Go to the tab with the specific number, 1-indexed. goto_tab: usize, + /// Move current tab to a position + move_current_tab: isize, + /// Toggle the tab overview. /// This only works with libadwaita enabled currently. toggle_tab_overview: void, @@ -646,6 +649,7 @@ pub const Action = union(enum) { .next_tab, .last_tab, .goto_tab, + .move_current_tab, .toggle_tab_overview, .new_split, .goto_split, From 23927d1fda61ec05c66fd760f00165f1cef89e07 Mon Sep 17 00:00:00 2001 From: axdank Date: Thu, 24 Oct 2024 00:11:04 -0300 Subject: [PATCH 2/5] removing unnecessary enum --- src/apprt/action.zig | 6 ------ 1 file changed, 6 deletions(-) diff --git a/src/apprt/action.zig b/src/apprt/action.zig index e9c1f1005..703becbd8 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -322,12 +322,6 @@ pub const GotoTab = enum(c_int) { _, }; -/// Move current tab . -pub const MoveCurrentTabDirection = enum(c_int) { - right, - left, -}; - /// The fullscreen mode to toggle to if we're moving to fullscreen. pub const Fullscreen = enum(c_int) { native, From 520dda65cbd50a48b45acf834b830f2938749492 Mon Sep 17 00:00:00 2001 From: axdank Date: Fri, 25 Oct 2024 08:07:11 -0300 Subject: [PATCH 3/5] apply review changes --- src/Surface.zig | 4 ++-- src/apprt/action.zig | 8 +++++--- src/apprt/glfw.zig | 2 +- src/apprt/gtk/App.zig | 8 ++++---- src/apprt/gtk/Window.zig | 2 +- src/apprt/gtk/notebook.zig | 32 +++++++++++++++++--------------- src/input/Binding.zig | 8 +++++--- 7 files changed, 35 insertions(+), 29 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index dca9c6316..0d39e6dcd 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3913,9 +3913,9 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool }, ), - .move_current_tab => |position| try self.rt_app.performAction( + .move_tab => |position| try self.rt_app.performAction( .{ .surface = self }, - .move_current_tab, + .move_tab, position, ), diff --git a/src/apprt/action.zig b/src/apprt/action.zig index 703becbd8..20d480e63 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -100,8 +100,10 @@ pub const Action = union(Key) { /// Toggle the visibility of all Ghostty terminal windows. toggle_visibility, - /// Move current tab given a position - move_current_tab: isize, + /// 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, /// Jump to a specific tab. Must handle the scenario that the tab /// value is invalid. @@ -193,7 +195,7 @@ pub const Action = union(Key) { toggle_window_decorations, toggle_quick_terminal, toggle_visibility, - move_current_tab, + move_tab, goto_tab, goto_split, resize_split, diff --git a/src/apprt/glfw.zig b/src/apprt/glfw.zig index 49eecab32..1dde97c9c 100644 --- a/src/apprt/glfw.zig +++ b/src/apprt/glfw.zig @@ -213,7 +213,7 @@ pub const App = struct { .toggle_quick_terminal, .toggle_visibility, .goto_tab, - .move_current_tab, + .move_tab, .inspector, .render_inspector, .quit_timer, diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 595e8e5b6..6b302493f 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -456,7 +456,7 @@ pub fn performAction( .new_tab => try self.newTab(target), .goto_tab => self.gotoTab(target, value), - .move_current_tab => self.moveCurrentTab(target, value), + .move_tab => self.moveTab(target, value), .new_split => try self.newSplit(target, value), .resize_split => self.resizeSplit(target, value), .equalize_splits => self.equalizeSplits(target), @@ -528,19 +528,19 @@ fn gotoTab(_: *App, target: apprt.Target, tab: apprt.action.GotoTab) void { } } -fn moveCurrentTab(_: *App, target: apprt.Target, position: isize) void { +fn moveTab(_: *App, target: apprt.Target, position: isize) void { switch (target) { .app => {}, .surface => |v| { const window = v.rt_surface.container.window() orelse { log.info( - "moveCurrentTab invalid for container={s}", + "moveTab invalid for container={s}", .{@tagName(v.rt_surface.container)}, ); return; }; - window.moveCurrentTab(v.rt_surface, @intCast(position)); + window.moveTab(v.rt_surface, @intCast(position)); }, } } diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index 8fb6cac77..1ef6f9c24 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -457,7 +457,7 @@ pub fn gotoNextTab(self: *Window, surface: *Surface) void { } /// Move the current tab for a surface. -pub fn moveCurrentTab(self: *Window, surface: *Surface, position: c_int) void { +pub fn moveTab(self: *Window, surface: *Surface, position: c_int) void { const tab = surface.container.tab() orelse { log.info("surface is not attached to a tab bar, cannot navigate", .{}); return; diff --git a/src/apprt/gtk/notebook.zig b/src/apprt/gtk/notebook.zig index dce729989..46245ce99 100644 --- a/src/apprt/gtk/notebook.zig +++ b/src/apprt/gtk/notebook.zig @@ -184,6 +184,22 @@ pub const Notebook = union(enum) { } pub fn moveTab(self: Notebook, tab: *Tab, position: c_int) void { + const page_idx = self.getTabPosition(tab) orelse return; + + const max = self.nPages() -| 1; + var new_position: c_int = page_idx + position; + + if (new_position < 0) { + new_position = max + new_position + 1; + } else if (new_position > max) { + new_position = new_position - max - 1; + } + + if (new_position == page_idx) return; + self.reorderPage(tab, new_position); + } + + pub fn reorderPage(self: Notebook, tab: *Tab, position: c_int) void { switch (self) { .gtk_notebook => |notebook| { c.gtk_notebook_reorder_child(notebook, @ptrCast(tab.box), position); @@ -191,21 +207,7 @@ pub const Notebook = union(enum) { .adw_tab_view => |tab_view| { if (comptime !adwaita.versionAtLeast(0, 0, 0)) unreachable; const page = c.adw_tab_view_get_page(tab_view, @ptrCast(tab.box)); - - const page_idx = self.getTabPosition(tab) orelse return; - - const max = self.nPages() -| 1; - var new_position: c_int = page_idx + position; - - if (new_position < 0) { - new_position = max; - } else if (new_position > max) { - new_position = 0; - } - - if (new_position == page_idx) return; - - _ = c.adw_tab_view_reorder_page(tab_view, page, new_position); + _ = c.adw_tab_view_reorder_page(tab_view, page, position); }, } } diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 943a704c4..c4d5c6cdf 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -300,8 +300,10 @@ pub const Action = union(enum) { /// Go to the tab with the specific number, 1-indexed. goto_tab: usize, - /// Move current tab to a position - move_current_tab: isize, + /// 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, /// Toggle the tab overview. /// This only works with libadwaita enabled currently. @@ -649,7 +651,7 @@ pub const Action = union(enum) { .next_tab, .last_tab, .goto_tab, - .move_current_tab, + .move_tab, .toggle_tab_overview, .new_split, .goto_split, From de5ec5d83e19e0026a5b76f2cd18951e7c129b7f Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Fri, 25 Oct 2024 11:53:45 -0700 Subject: [PATCH 4/5] macos: make move_tab work --- include/ghostty.h | 8 ++++ macos/Ghostty.xcodeproj/project.pbxproj | 4 ++ .../Terminal/TerminalController.swift | 43 +++++++++++++++++++ macos/Sources/Ghostty/Ghostty.Action.swift | 15 +++++++ macos/Sources/Ghostty/Ghostty.App.swift | 28 ++++++++++++ macos/Sources/Ghostty/Package.swift | 10 ++++- src/Surface.zig | 2 +- src/apprt/action.zig | 12 ++++-- 8 files changed, 117 insertions(+), 5 deletions(-) create mode 100644 macos/Sources/Ghostty/Ghostty.Action.swift diff --git a/include/ghostty.h b/include/ghostty.h index 0f4c65f56..ca70456d8 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -15,6 +15,7 @@ extern "C" { #include #include #include +#include //------------------------------------------------------------------- // 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; diff --git a/macos/Ghostty.xcodeproj/project.pbxproj b/macos/Ghostty.xcodeproj/project.pbxproj index 57070dc47..414719f10 100644 --- a/macos/Ghostty.xcodeproj/project.pbxproj +++ b/macos/Ghostty.xcodeproj/project.pbxproj @@ -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 = ""; }; A53426342A7DA53D00EBB7A2 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; A535B9D9299C569B0017E2E4 /* ErrorView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorView.swift; sourceTree = ""; }; + A53A6C022CCC1B7D00943E98 /* Ghostty.Action.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.Action.swift; sourceTree = ""; }; A53D0C932B53B43700305CE6 /* iOSApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = iOSApp.swift; sourceTree = ""; }; A53D0C992B543F3B00305CE6 /* Ghostty.App.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Ghostty.App.swift; sourceTree = ""; }; A54D786B2CA79788001B19B1 /* BaseTerminalController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseTerminalController.swift; sourceTree = ""; }; @@ -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 */, diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index 0b1ff3b72..b5bdc43df 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -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 } diff --git a/macos/Sources/Ghostty/Ghostty.Action.swift b/macos/Sources/Ghostty/Ghostty.Action.swift new file mode 100644 index 000000000..d9e58b28c --- /dev/null +++ b/macos/Sources/Ghostty/Ghostty.Action.swift @@ -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 + } + } +} diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index a2ed8903a..e5320a24a 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -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, diff --git a/macos/Sources/Ghostty/Package.swift b/macos/Sources/Ghostty/Package.swift index f6fab532e..1be99d4a5 100644 --- a/macos/Sources/Ghostty/Package.swift +++ b/macos/Sources/Ghostty/Package.swift @@ -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" diff --git a/src/Surface.zig b/src/Surface.zig index 0d39e6dcd..b984ea997 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -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( diff --git a/src/apprt/action.zig b/src/apprt/action.zig index 20d480e63..2c37ca270 100644 --- a/src/apprt/action.zig +++ b/src/apprt/action.zig @@ -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. From e7bd60b28ef83e01a15f2561316cc0a463dea1fa Mon Sep 17 00:00:00 2001 From: axdank Date: Fri, 25 Oct 2024 16:53:13 -0300 Subject: [PATCH 5/5] fix type mismatch in `moveTab` function parameter --- src/apprt/gtk/App.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/apprt/gtk/App.zig b/src/apprt/gtk/App.zig index 6b302493f..af664d720 100644 --- a/src/apprt/gtk/App.zig +++ b/src/apprt/gtk/App.zig @@ -528,7 +528,7 @@ fn gotoTab(_: *App, target: apprt.Target, tab: apprt.action.GotoTab) void { } } -fn moveTab(_: *App, target: apprt.Target, position: isize) void { +fn moveTab(_: *App, target: apprt.Target, move_tab: apprt.action.MoveTab) void { switch (target) { .app => {}, .surface => |v| { @@ -540,7 +540,7 @@ fn moveTab(_: *App, target: apprt.Target, position: isize) void { return; }; - window.moveTab(v.rt_surface, @intCast(position)); + window.moveTab(v.rt_surface, @intCast(move_tab.amount)); }, } }