macOS: restore keyboard focus after inline tab title edit

After finishing an inline tab title edit (via keybind or double-click),
`TabTitleEditor.finishEditing()` calls `makeFirstResponder(nil)` to
clear focus from the text field, leaving the window itself as first
responder. No code path restores focus to the terminal surface, so all
keyboard input is lost until the user clicks into a pane.

Add a `tabTitleEditorDidFinishEditing` delegate callback that fires
after every edit (commit or cancel). TerminalWindow implements it by
calling `makeFirstResponder(focusedSurface)` to hand focus back to the
terminal.

Fixes https://github.com/ghostty-org/ghostty/discussions/11315

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
chronologos
2026-03-10 06:58:44 -07:00
committed by Mitchell Hashimoto
parent f8a0a45963
commit 7629130fb4
2 changed files with 23 additions and 2 deletions

View File

@@ -835,4 +835,13 @@ extension TerminalWindow: TabTitleEditorDelegate {
guard let targetController = targetWindow.windowController as? BaseTerminalController else { return }
targetController.promptTabTitle()
}
func tabTitleEditor(_ editor: TabTitleEditor, didFinishEditing targetWindow: NSWindow) {
// After inline editing, the first responder is the window itself.
// Restore focus to the terminal surface so keyboard input works.
guard let controller = windowController as? BaseTerminalController,
let focusedSurface = controller.focusedSurface
else { return }
makeFirstResponder(focusedSurface)
}
}

View File

@@ -26,6 +26,12 @@ protocol TabTitleEditorDelegate: AnyObject {
_ editor: TabTitleEditor,
performFallbackRenameFor targetWindow: NSWindow
)
/// Called after inline editing finishes (whether committed or cancelled).
/// Use this to restore focus to the appropriate responder.
func tabTitleEditor(
_ editor: TabTitleEditor,
didFinishEditing targetWindow: NSWindow)
}
/// Handles inline tab title editing for native AppKit window tabs.
@@ -212,8 +218,14 @@ final class TabTitleEditor: NSObject, NSTextFieldDelegate {
previousTabState = nil
// Delegate owns title persistence semantics (including empty-title handling).
guard commit, let targetWindow else { return }
delegate?.tabTitleEditor(self, didCommitTitle: editedTitle, for: targetWindow)
guard let targetWindow else { return }
if commit {
delegate?.tabTitleEditor(self, didCommitTitle: editedTitle, for: targetWindow)
}
// Notify delegate that editing is done so it can restore focus.
delegate?.tabTitleEditor(self, didFinishEditing: targetWindow)
}
/// Chooses an editor frame that aligns with the tab title within the tab button.