diff --git a/src/main.rs b/src/main.rs index 63da8df..530473d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -298,11 +298,19 @@ impl TerminalWindow { fn sync_title(&self) { let title = self.active().title(); - self.window.set_title(title); #[cfg(target_os = "macos")] if let Some(ns_view) = ns_view_ptr(&self.window) { - platform::set_tab_title(ns_view, title); + let (idx, count) = platform::tab_index_and_count(ns_view); + let labeled = if count > 1 { + format!("{idx} · {title}") + } else { + title.to_string() + }; + self.window.set_title(&labeled); + platform::set_tab_title(ns_view, &labeled); + return; } + self.window.set_title(title); } fn accept_ghost(&mut self) { @@ -999,6 +1007,9 @@ impl App { if let Some(ns_view) = self.windows.get(&window_id).and_then(|tw| ns_view_ptr(&tw.window)) { platform::setup_add_tab_button(ns_view); } + + // Re-number all tab labels now that a new tab has been added. + self.sync_all_titles(); } fn add_split(&mut self, window_id: WindowId, dir: SplitDir) { @@ -1101,6 +1112,16 @@ impl App { } if self.windows.is_empty() { event_loop.exit(); + } else { + self.sync_all_titles(); + } + } + + /// Re-sync every window's tab label. Call this whenever the tab count + /// changes so that all index prefixes stay accurate. + fn sync_all_titles(&self) { + for tw in self.windows.values() { + tw.sync_title(); } } } @@ -1248,6 +1269,9 @@ impl ApplicationHandler for App { if let Some(ns_view) = self.windows.get(&window_id).and_then(|tw| ns_view_ptr(&tw.window)) { platform::setup_add_tab_button(ns_view); } + + // Re-number all tab labels now that a new tab has been added. + self.sync_all_titles(); } fn window_event( @@ -1261,6 +1285,12 @@ impl ApplicationHandler for App { self.close_window(window_id, event_loop); } + // Re-sync all tab numbers when a window gains focus — this fires + // after a drag-reorder because macOS activates the moved tab. + WindowEvent::Focused(true) => { + self.sync_all_titles(); + } + WindowEvent::RedrawRequested => { if let Some(tw) = self.windows.get_mut(&window_id) { tw.redraw(); diff --git a/src/platform.rs b/src/platform.rs index a20a938..d692823 100644 --- a/src/platform.rs +++ b/src/platform.rs @@ -174,6 +174,7 @@ pub fn clipboard_text() -> Option { /// Add `new_win` as a tab in the same tabbed-window group as `existing_win`. /// Both pointers must be NSView* obtained from winit's raw window handle. /// Safe to call immediately after window creation. +/// The new tab is always appended at the end of the tab bar. #[cfg(target_os = "macos")] pub fn add_window_as_tab(existing_ns_view: *mut std::ffi::c_void, new_ns_view: *mut std::ffi::c_void) { use objc2::{msg_send, runtime::AnyObject}; @@ -182,10 +183,24 @@ pub fn add_window_as_tab(existing_ns_view: *mut std::ffi::c_void, new_ns_view: * let nv = new_ns_view as *mut AnyObject; let ew: *mut AnyObject = msg_send![ev, window]; let nw: *mut AnyObject = msg_send![nv, window]; - if !ew.is_null() && !nw.is_null() { - // NSWindowOrderingMode::NSWindowAbove = 1 - let _: () = msg_send![ew, addTabbedWindow: nw ordered: 1i64]; - } + if ew.is_null() || nw.is_null() { return; } + + // Find the last window in the tab group so we append after it, + // rather than inserting after whichever window we happened to pick. + let tabs: *mut AnyObject = msg_send![ew, tabbedWindows]; + let last_win: *mut AnyObject = if !tabs.is_null() { + let count: usize = msg_send![tabs, count]; + if count > 0 { + msg_send![tabs, objectAtIndex: count - 1] + } else { + ew + } + } else { + ew + }; + + // NSWindowOrderingMode::NSWindowAbove = 1 → insert after `last_win` + let _: () = msg_send![last_win, addTabbedWindow: nw ordered: 1i64]; } } @@ -236,6 +251,30 @@ pub fn select_tab_at_index(ns_view: *mut std::ffi::c_void, n: usize) { } } +/// Return the 1-based index of this window in its tab group, plus the total +/// number of tabs. Returns `(1, 1)` when the window is not part of a group. +#[cfg(target_os = "macos")] +pub fn tab_index_and_count(ns_view: *mut std::ffi::c_void) -> (usize, usize) { + use objc2::{msg_send, runtime::AnyObject}; + unsafe { + let view: *mut AnyObject = ns_view as *mut AnyObject; + let win: *mut AnyObject = msg_send![view, window]; + if win.is_null() { return (1, 1); } + let tabs: *mut AnyObject = msg_send![win, tabbedWindows]; + if tabs.is_null() { return (1, 1); } + let count: usize = msg_send![tabs, count]; + if count == 0 { return (1, 1); } + for i in 0..count { + let w: *mut AnyObject = msg_send![tabs, objectAtIndex: i]; + // Compare by pointer identity (both are NSWindow*). + if std::ptr::eq(w, win) { + return (i + 1, count); + } + } + (1, count) + } +} + /// Update the native macOS tab label for this window. /// `NSWindowTab.title` is separate from `NSWindow.title` and doesn't /// automatically follow it after the window is added to a tab group.