From 7036676ec05484e461ea7fa3efa6a2b2396476ae Mon Sep 17 00:00:00 2001 From: Mitchell Hashimoto Date: Wed, 22 Nov 2023 11:40:52 -0800 Subject: [PATCH] macos: relabel tabs when they're reordered with the mouse Prior to this commit, the shortcut shown on the tab would not be updated until a focus change occurred. There is no event for this so the way we do it is by listening for the tab view frame to change, comparing the window state, and then updating labels. --- .../Terminal/TerminalController.swift | 40 +++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/macos/Sources/Features/Terminal/TerminalController.swift b/macos/Sources/Features/Terminal/TerminalController.swift index 2f5772324..cdbad1b84 100644 --- a/macos/Sources/Features/Terminal/TerminalController.swift +++ b/macos/Sources/Features/Terminal/TerminalController.swift @@ -36,6 +36,15 @@ class TerminalController: NSWindowController, NSWindowDelegate, /// The clipboard confirmation window, if shown. private var clipboardConfirmation: ClipboardConfirmationController? = nil + + /// This is set to true when we care about frame changes. This is a small optimization since + /// this controller registers a listener for ALL frame change notifications and this lets us bail + /// early if we don't care. + private var tabListenForFrame: Bool = false + + /// This is the hash value of the last tabGroup.windows array. We use this to detect order + /// changes in the list. + private var tabWindowsHash: Int = 0 init(_ ghostty: Ghostty.AppState, withBaseConfig base: Ghostty.SurfaceConfiguration? = nil) { self.ghostty = ghostty @@ -62,6 +71,11 @@ class TerminalController: NSWindowController, NSWindowDelegate, selector: #selector(onConfirmClipboardRequest), name: Ghostty.Notification.confirmClipboard, object: nil) + center.addObserver( + self, + selector: #selector(onFrameDidChange), + name: NSView.frameDidChangeNotification, + object: nil) } required init?(coder: NSCoder) { @@ -73,13 +87,23 @@ class TerminalController: NSWindowController, NSWindowDelegate, let center = NotificationCenter.default center.removeObserver(self) } + + //MARK: - Methods /// Update the accessory view of each tab according to the keyboard /// shortcut that activates it (if any). This is called when the key window /// changes and when a window is closed. func relabelTabs() { + // Reset this to false. It'll be set back to true later. + tabListenForFrame = false + guard let windows = self.window?.tabbedWindows else { return } guard let cfg = ghostty.config else { return } + + // We only listen for frame changes if we have more than 1 window, + // otherwise the accessory view doesn't matter. + tabListenForFrame = windows.count > 1 + for (index, window) in windows.enumerated().prefix(9) { let action = "goto_tab:\(index + 1)" let trigger = ghostty_config_trigger(cfg, action, UInt(action.count)) @@ -94,9 +118,25 @@ class TerminalController: NSWindowController, NSWindowDelegate, let attributedString = NSAttributedString(string: " \(equiv) ", attributes: attributes) let text = NSTextField(labelWithAttributedString: attributedString) text.setContentCompressionResistancePriority(.windowSizeStayPut, for: .horizontal) + text.postsFrameChangedNotifications = true window.tab.accessoryView = text } } + + @objc private func onFrameDidChange(_ notification: NSNotification) { + // This is a huge hack to set the proper shortcut for tab selection + // on tab reordering using the mouse. There is no event, delegate, etc. + // as far as I can tell for when a tab is manually reordered with the + // mouse in a macOS-native tab group, so the way we detect it is setting + // the accessoryView "postsFrameChangedNotification" to true, listening + // for the view frame to change, comparing the windows list, and + // relabeling the tabs. + guard tabListenForFrame else { return } + guard let v = self.window?.tabbedWindows?.hashValue else { return } + guard tabWindowsHash != v else { return } + tabWindowsHash = v + self.relabelTabs() + } //MARK: - NSWindowController