From 262902a28d2d490b6e9feb78684c2d2747b88e0e Mon Sep 17 00:00:00 2001 From: Eduardo Dominguez Date: Mon, 19 Aug 2024 12:19:00 -0600 Subject: [PATCH 1/6] macos: jump to last_tab --- include/ghostty.h | 1 + .../Features/Terminal/TerminalController.swift | 2 ++ macos/Sources/Ghostty/Ghostty.App.swift | 8 ++++++++ src/Surface.zig | 13 +++++++++++++ src/apprt/embedded.zig | 10 ++++++++++ src/config/Config.zig | 10 ++++++++++ src/input/Binding.zig | 3 +++ 7 files changed, 47 insertions(+) diff --git a/include/ghostty.h b/include/ghostty.h index b56b8827e..b413dec41 100644 --- a/include/ghostty.h +++ b/include/ghostty.h @@ -143,6 +143,7 @@ typedef enum { typedef enum { GHOSTTY_TAB_PREVIOUS = -1, GHOSTTY_TAB_NEXT = -2, + GHOSTTY_TAB_LAST = -3, } ghostty_tab_e; typedef enum { diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index 729757dd2..c154fa3e1 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -724,6 +724,8 @@ class TerminalController: NSWindowController, NSWindowDelegate, } else { finalIndex = selectedIndex + 1 } + } else if (tabIndex == GHOSTTY_TAB_LAST.rawValue) { + finalIndex = tabbedWindows.count - 1 } else { return } diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index b4fe17f86..c56a53b77 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -379,6 +379,14 @@ extension Ghostty { ) } + static func gotoLastTab(_ userdata: UnsafeMutableRawPointer?) { + let surface = self.surfaceUserdata(from: userdata) + NotificationCenter.default.post( + name: Notification.ghosttyGotoTab, + object: surface + ) + } + static func readClipboard(_ userdata: UnsafeMutableRawPointer?, location: ghostty_clipboard_e, state: UnsafeMutableRawPointer?) { // If we don't even have a surface, something went terrible wrong so we have // to leak "state". diff --git a/src/Surface.zig b/src/Surface.zig index 4b5b3f98c..08db61c10 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3429,6 +3429,19 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool } else log.warn("runtime doesn't implement gotoNextTab", .{}); }, + .last_tab => { + if (@hasDecl(apprt.Surface, "hasTabs")) { + if (!self.rt_surface.hasTabs()) { + log.debug("surface has no tabs, ignoring last_tab binding", .{}); + return false; + } + } + + if (@hasDecl(apprt.Surface, "gotoLastTab")) { + self.rt_surface.gotoLastTab(); + } else log.warn("runtime doesn't implement gotoLastTab", .{}); + }, + .goto_tab => |n| { if (@hasDecl(apprt.Surface, "gotoTab")) { self.rt_surface.gotoTab(n); diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index d3b2d6f29..fff14967d 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -139,6 +139,7 @@ pub const App = struct { const GotoTab = enum(i32) { previous = -1, next = -2, + last = -3, _, }; @@ -1008,6 +1009,15 @@ pub const Surface = struct { func(self.userdata, @enumFromInt(idx)); } + pub fn gotoLastTab(self: *Surface) void { + const func = self.app.opts.goto_tab orelse { + log.info("runtime embedder does not goto_tab", .{}); + return; + }; + + func(self.userdata, .last); + } + pub fn gotoPreviousTab(self: *Surface) void { const func = self.app.opts.goto_tab orelse { log.info("runtime embedder does not goto_tab", .{}); diff --git a/src/config/Config.zig b/src/config/Config.zig index 6d45ab4ec..a6327c063 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1582,6 +1582,11 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config { .{ .key = .{ .translated = .right }, .mods = .{ .ctrl = true, .shift = true } }, .{ .next_tab = {} }, ); + try result.keybind.set.put( + alloc, + .{ .key = .{ .physical = inputpkg.Key.zero }, .mods = .{ .super = true } }, + .{ .last_tab = {} }, + ); try result.keybind.set.put( alloc, .{ .key = .{ .translated = .page_up }, .mods = .{ .ctrl = true } }, @@ -1850,6 +1855,11 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config { .{ .key = .{ .translated = .right_bracket }, .mods = .{ .super = true, .shift = true } }, .{ .next_tab = {} }, ); + try result.keybind.set.put( + alloc, + .{ .key = .{ .physical = inputpkg.Key.zero }, .mods = .{ .super = true } }, + .{ .last_tab = {} }, + ); try result.keybind.set.put( alloc, .{ .key = .{ .translated = .d }, .mods = .{ .super = true } }, diff --git a/src/input/Binding.zig b/src/input/Binding.zig index ef0f8e4c1..a4002ddc9 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -257,6 +257,9 @@ pub const Action = union(enum) { /// Go to the next tab. next_tab: void, + /// Go to the last tab (the one with the highest index) + last_tab: void, + /// Go to the tab with the specific number, 1-indexed. goto_tab: usize, From 02c6fb5a8ca2464a53668ca48d27f0c541a8070b Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 26 Aug 2024 19:56:51 -0700 Subject: [PATCH 2/6] config: remove the super+0 binding for last_tab --- src/config/Config.zig | 5 ----- 1 file changed, 5 deletions(-) diff --git a/src/config/Config.zig b/src/config/Config.zig index a6327c063..a090b5d60 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -1582,11 +1582,6 @@ pub fn default(alloc_gpa: Allocator) Allocator.Error!Config { .{ .key = .{ .translated = .right }, .mods = .{ .ctrl = true, .shift = true } }, .{ .next_tab = {} }, ); - try result.keybind.set.put( - alloc, - .{ .key = .{ .physical = inputpkg.Key.zero }, .mods = .{ .super = true } }, - .{ .last_tab = {} }, - ); try result.keybind.set.put( alloc, .{ .key = .{ .translated = .page_up }, .mods = .{ .ctrl = true } }, From 3d1ee3daa8dea8f5f332d9db52b7cc72171cc9cf Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 26 Aug 2024 20:09:45 -0700 Subject: [PATCH 3/6] apprt: make gotoTab handle all tab movements --- src/Surface.zig | 20 +++++++++--------- src/apprt.zig | 1 + src/apprt/embedded.zig | 46 +++--------------------------------------- src/apprt/structs.zig | 10 +++++++++ 4 files changed, 24 insertions(+), 53 deletions(-) diff --git a/src/Surface.zig b/src/Surface.zig index 08db61c10..11c3190a5 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -3411,9 +3411,9 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool } } - if (@hasDecl(apprt.Surface, "gotoPreviousTab")) { - self.rt_surface.gotoPreviousTab(); - } else log.warn("runtime doesn't implement gotoPreviousTab", .{}); + if (@hasDecl(apprt.Surface, "gotoTab")) { + self.rt_surface.gotoTab(.previous); + } else log.warn("runtime doesn't implement gotoTab", .{}); }, .next_tab => { @@ -3424,9 +3424,9 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool } } - if (@hasDecl(apprt.Surface, "gotoNextTab")) { - self.rt_surface.gotoNextTab(); - } else log.warn("runtime doesn't implement gotoNextTab", .{}); + if (@hasDecl(apprt.Surface, "gotoTab")) { + self.rt_surface.gotoTab(.next); + } else log.warn("runtime doesn't implement gotoTab", .{}); }, .last_tab => { @@ -3437,14 +3437,14 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool } } - if (@hasDecl(apprt.Surface, "gotoLastTab")) { - self.rt_surface.gotoLastTab(); - } else log.warn("runtime doesn't implement gotoLastTab", .{}); + if (@hasDecl(apprt.Surface, "gotoTab")) { + self.rt_surface.gotoTab(.last); + } else log.warn("runtime doesn't implement gotoTab", .{}); }, .goto_tab => |n| { if (@hasDecl(apprt.Surface, "gotoTab")) { - self.rt_surface.gotoTab(n); + self.rt_surface.gotoTab(@enumFromInt(n)); } else log.warn("runtime doesn't implement gotoTab", .{}); }, diff --git a/src/apprt.zig b/src/apprt.zig index 767fc57e6..491f1b8b5 100644 --- a/src/apprt.zig +++ b/src/apprt.zig @@ -28,6 +28,7 @@ pub const ClipboardRequestType = structs.ClipboardRequestType; pub const ColorScheme = structs.ColorScheme; pub const CursorPos = structs.CursorPos; pub const DesktopNotification = structs.DesktopNotification; +pub const GotoTab = structs.GotoTab; pub const IMEPos = structs.IMEPos; pub const Selection = structs.Selection; pub const SplitDirection = structs.SplitDirection; diff --git a/src/apprt/embedded.zig b/src/apprt/embedded.zig index fff14967d..52b42fe2a 100644 --- a/src/apprt/embedded.zig +++ b/src/apprt/embedded.zig @@ -108,7 +108,7 @@ pub const App = struct { toggle_split_zoom: ?*const fn (SurfaceUD) callconv(.C) void = null, /// Goto tab - goto_tab: ?*const fn (SurfaceUD, GotoTab) callconv(.C) void = null, + goto_tab: ?*const fn (SurfaceUD, apprt.GotoTab) callconv(.C) void = null, /// Toggle fullscreen for current window. toggle_fullscreen: ?*const fn (SurfaceUD, configpkg.NonNativeFullscreen) callconv(.C) void = null, @@ -135,14 +135,6 @@ pub const App = struct { mouse_over_link: ?*const fn (SurfaceUD, ?[*]const u8, usize) void = null, }; - /// Special values for the goto_tab callback. - const GotoTab = enum(i32) { - previous = -1, - next = -2, - last = -3, - _, - }; - core_app: *CoreApp, config: *const Config, opts: Options, @@ -995,45 +987,13 @@ pub const Surface = struct { }; } - pub fn gotoTab(self: *Surface, n: usize) void { + pub fn gotoTab(self: *Surface, tab: apprt.GotoTab) void { const func = self.app.opts.goto_tab orelse { log.info("runtime embedder does not goto_tab", .{}); return; }; - const idx = std.math.cast(i32, n) orelse { - log.warn("cannot cast tab index to i32 n={}", .{n}); - return; - }; - - func(self.userdata, @enumFromInt(idx)); - } - - pub fn gotoLastTab(self: *Surface) void { - const func = self.app.opts.goto_tab orelse { - log.info("runtime embedder does not goto_tab", .{}); - return; - }; - - func(self.userdata, .last); - } - - pub fn gotoPreviousTab(self: *Surface) void { - const func = self.app.opts.goto_tab orelse { - log.info("runtime embedder does not goto_tab", .{}); - return; - }; - - func(self.userdata, .previous); - } - - pub fn gotoNextTab(self: *Surface) void { - const func = self.app.opts.goto_tab orelse { - log.info("runtime embedder does not goto_tab", .{}); - return; - }; - - func(self.userdata, .next); + func(self.userdata, tab); } pub fn toggleFullscreen(self: *Surface, nonNativeFullscreen: configpkg.NonNativeFullscreen) void { diff --git a/src/apprt/structs.zig b/src/apprt/structs.zig index 1982cc497..1e14b1b7c 100644 --- a/src/apprt/structs.zig +++ b/src/apprt/structs.zig @@ -62,6 +62,16 @@ pub const DesktopNotification = struct { body: []const u8, }; +/// 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. +pub const GotoTab = enum(c_int) { + previous = -1, + next = -2, + last = -3, + _, +}; + // This is made extern (c_int) to make interop easier with our embedded // runtime. The small size cost doesn't make a difference in our union. pub const SplitDirection = enum(c_int) { From d7e7f559562f681747792fb4c58ab070a9345127 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 26 Aug 2024 20:09:45 -0700 Subject: [PATCH 4/6] apprt/gtk: support last_tab --- src/apprt/gtk/Surface.zig | 33 +++++++-------------------------- src/apprt/gtk/Window.zig | 7 +++++++ 2 files changed, 14 insertions(+), 26 deletions(-) diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 7ae690ce5..8e393a2bc 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -795,31 +795,7 @@ pub fn hasTabs(self: *const Surface) bool { return window.hasTabs(); } -pub fn gotoPreviousTab(self: *Surface) void { - const window = self.container.window() orelse { - log.info( - "gotoPreviousTab invalid for container={s}", - .{@tagName(self.container)}, - ); - return; - }; - - window.gotoPreviousTab(self); -} - -pub fn gotoNextTab(self: *Surface) void { - const window = self.container.window() orelse { - log.info( - "gotoNextTab invalid for container={s}", - .{@tagName(self.container)}, - ); - return; - }; - - window.gotoNextTab(self); -} - -pub fn gotoTab(self: *Surface, n: usize) void { +pub fn gotoTab(self: *Surface, tab: apprt.GotoTab) void { const window = self.container.window() orelse { log.info( "gotoTab invalid for container={s}", @@ -828,7 +804,12 @@ pub fn gotoTab(self: *Surface, n: usize) void { return; }; - window.gotoTab(n); + switch (tab) { + .previous => window.gotoPreviousTab(self), + .next => window.gotoNextTab(self), + .last => window.gotoLastTab(), + else => window.gotoTab(@intFromEnum(tab)), + } } pub fn setShouldClose(self: *Surface) void { diff --git a/src/apprt/gtk/Window.zig b/src/apprt/gtk/Window.zig index 1615b986f..9df7a04ed 100644 --- a/src/apprt/gtk/Window.zig +++ b/src/apprt/gtk/Window.zig @@ -288,6 +288,13 @@ pub fn gotoNextTab(self: *Window, surface: *Surface) void { self.focusCurrentTab(); } +/// Go to the next tab for a surface. +pub fn gotoLastTab(self: *Window) void { + const max = c.gtk_notebook_get_n_pages(self.notebook) -| 1; + c.gtk_notebook_set_current_page(self.notebook, max); + self.focusCurrentTab(); +} + /// Go to the specific tab index. pub fn gotoTab(self: *Window, n: usize) void { if (n == 0) return; From 512b24818a2f9b5b68753ac4f4605059ba47c624 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 26 Aug 2024 20:16:20 -0700 Subject: [PATCH 5/6] apprt/gtk: fix int cast --- src/apprt/gtk/Surface.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/apprt/gtk/Surface.zig b/src/apprt/gtk/Surface.zig index 8e393a2bc..fd7b62cdc 100644 --- a/src/apprt/gtk/Surface.zig +++ b/src/apprt/gtk/Surface.zig @@ -808,7 +808,7 @@ pub fn gotoTab(self: *Surface, tab: apprt.GotoTab) void { .previous => window.gotoPreviousTab(self), .next => window.gotoNextTab(self), .last => window.gotoLastTab(), - else => window.gotoTab(@intFromEnum(tab)), + else => window.gotoTab(@intCast(@intFromEnum(tab))), } } From 866a7dfcf15ec9ed56d0f9a0023aecf6c64b49f8 Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Mon, 26 Aug 2024 20:17:15 -0700 Subject: [PATCH 6/6] macos: remove unused gotolasttab --- macos/Sources/Ghostty/Ghostty.App.swift | 8 -------- 1 file changed, 8 deletions(-) diff --git a/macos/Sources/Ghostty/Ghostty.App.swift b/macos/Sources/Ghostty/Ghostty.App.swift index c56a53b77..b4fe17f86 100644 --- a/macos/Sources/Ghostty/Ghostty.App.swift +++ b/macos/Sources/Ghostty/Ghostty.App.swift @@ -379,14 +379,6 @@ extension Ghostty { ) } - static func gotoLastTab(_ userdata: UnsafeMutableRawPointer?) { - let surface = self.surfaceUserdata(from: userdata) - NotificationCenter.default.post( - name: Notification.ghosttyGotoTab, - object: surface - ) - } - static func readClipboard(_ userdata: UnsafeMutableRawPointer?, location: ghostty_clipboard_e, state: UnsafeMutableRawPointer?) { // If we don't even have a surface, something went terrible wrong so we have // to leak "state".