diff --git a/src/App.zig b/src/App.zig index a2a81b687..fe542d8ee 100644 --- a/src/App.zig +++ b/src/App.zig @@ -95,7 +95,7 @@ pub fn create(alloc: Allocator, config: *const Config) !*App { errdefer if (comptime builtin.target.isDarwin()) app.darwin.deinit(); // Create the first window - try app.newWindow(.{}); + _ = try app.newWindow(.{}); return app; } @@ -148,7 +148,8 @@ fn drainMailbox(self: *App) !void { while (drain.next()) |message| { log.debug("mailbox message={s}", .{@tagName(message)}); switch (message) { - .new_window => |msg| try self.newWindow(msg), + .new_window => |msg| _ = try self.newWindow(msg), + .new_tab => |msg| try self.newTab(msg), .quit => try self.setQuit(), .window_message => |msg| try self.windowMessage(msg.window, msg.message), } @@ -156,7 +157,7 @@ fn drainMailbox(self: *App) !void { } /// Create a new window -fn newWindow(self: *App, msg: Message.NewWindow) !void { +fn newWindow(self: *App, msg: Message.NewWindow) !*Window { var window = try Window.create(self.alloc, self, self.config); errdefer window.destroy(); try self.windows.append(self.alloc, window); @@ -164,6 +165,33 @@ fn newWindow(self: *App, msg: Message.NewWindow) !void { // Set initial font size if given if (msg.font_size) |size| window.setFontSize(size); + + return window; +} + +/// Create a new tab in the parent window +fn newTab(self: *App, msg: Message.NewWindow) !void { + if (comptime !builtin.target.isDarwin()) { + log.warn("tabbing is not supported on this platform", .{}); + return; + } + + const parent = msg.parent orelse { + log.warn("parent must be set in new_tab message", .{}); + return; + }; + + // If the parent was closed prior to us handling the message, we do nothing. + if (!self.hasWindow(parent)) { + log.warn("new_tab parent is gone, not launching a new tab", .{}); + return; + } + + // Create the new window + const window = try self.newWindow(msg); + + // Add the window to our parent tab group + parent.addWindow(window); } /// Start quitting @@ -182,22 +210,32 @@ fn windowMessage(self: *App, win: *Window, msg: Window.Message) !void { // We want to ensure our window is still active. Window messages // are quite rare and we normally don't have many windows so we do // a simple linear search here. - for (self.windows.items) |window| { - if (window == win) { - try win.handleMessage(msg); - return; - } + if (self.hasWindow(win)) { + try win.handleMessage(msg); } // Window was not found, it probably quit before we handled the message. // Not a problem. } +fn hasWindow(self: *App, win: *Window) bool { + for (self.windows.items) |window| { + if (window == win) return true; + } + + return false; +} + /// The message types that can be sent to the app thread. pub const Message = union(enum) { /// Create a new terminal window. new_window: NewWindow, + /// Create a new tab within the tab group of the focused window. + /// This does nothing if we're on a platform or using a window + /// environment that doesn't support tabs. + new_tab: NewWindow, + /// Quit quit: void, @@ -208,6 +246,9 @@ pub const Message = union(enum) { }, const NewWindow = struct { + /// The parent window, only used for new tabs. + parent: ?*Window = null, + /// The font size to create the window with or null to default to /// the configuration amount. font_size: ?font.face.DesiredSize = null, diff --git a/src/Window.zig b/src/Window.zig index 42adc2e63..f98bc7b8f 100644 --- a/src/Window.zig +++ b/src/Window.zig @@ -542,6 +542,18 @@ pub fn shouldClose(self: Window) bool { return self.window.shouldClose(); } +/// Add a window to the tab group of this window. +pub fn addWindow(self: Window, other: *Window) void { + assert(builtin.target.isDarwin()); + + const NSWindowOrderingMode = enum(isize) { below = -1, out = 0, above = 1 }; + const nswindow = objc.Object.fromId(glfwNative.getCocoaWindow(self.window).?); + nswindow.msgSend(void, objc.sel("addTabbedWindow:ordered:"), .{ + objc.Object.fromId(glfwNative.getCocoaWindow(other.window).?), + NSWindowOrderingMode.above, + }); +} + /// Called from the app thread to handle mailbox messages to our specific /// window. pub fn handleMessage(self: *Window, msg: Message) !void { @@ -953,6 +965,20 @@ fn keyCallback( win.app.wakeup(); }, + .new_tab => { + _ = win.app.mailbox.push(.{ + .new_tab = .{ + .parent = win, + + .font_size = if (win.config.@"window-inherit-font-size") + win.font_size + else + null, + }, + }, .{ .instant = {} }); + win.app.wakeup(); + }, + .close_window => win.window.setShouldClose(true), .quit => { diff --git a/src/config.zig b/src/config.zig index 74b5e93d3..bac96785e 100644 --- a/src/config.zig +++ b/src/config.zig @@ -217,7 +217,12 @@ pub const Config = struct { .{ .key = .w, .mods = .{ .super = true } }, .{ .close_window = {} }, ); - if (builtin.os.tag == .macos) { + if (comptime builtin.target.isDarwin()) { + try result.keybind.set.put( + alloc, + .{ .key = .t, .mods = .{ .super = true } }, + .{ .new_tab = {} }, + ); try result.keybind.set.put( alloc, .{ .key = .q, .mods = .{ .super = true } }, diff --git a/src/input/Binding.zig b/src/input/Binding.zig index 8b909f7ad..e0629bd36 100644 --- a/src/input/Binding.zig +++ b/src/input/Binding.zig @@ -144,7 +144,10 @@ pub const Action = union(enum) { /// Open a new window new_window: void, - /// Close the current window + /// Open a new tab + new_tab: void, + + /// Close the current window or tab close_window: void, /// Quit ghostty