macOS: restore keyboard focus after inline tab title edit (#11320)

## Summary

- After finishing an inline tab title edit (via keybind or
double-click), all keyboard input is lost because
`TabTitleEditor.finishEditing()` sets `makeFirstResponder(nil)`, leaving
the window itself as first responder with no path back to the terminal
surface.
- Adds a `tabTitleEditorDidFinishEditing` delegate callback to
`TabTitleEditorDelegate` that fires after every edit (commit or cancel).
- `TerminalWindow` implements it by calling
`makeFirstResponder(focusedSurface)` to restore keyboard focus to the
terminal.

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

## Testing

- [x] Bind `prompt_tab_title` to a keybind (e.g. `keybind =
cmd+shift+i=prompt_tab_title`)
- [x] Trigger inline tab title edit via keybind, press Enter — verify
keyboard input works immediately
- [x] Trigger inline tab title edit via keybind, press Escape — verify
keyboard input works immediately
- [x] Double-click a tab title, press Enter — verify keyboard input
works immediately
- [x] Double-click a tab title, press Escape — verify keyboard input
works immediately
- [x] Verify Cmd+number tab switching works after all of the above
- [x] Verify split pane focus is correct after editing tab title with
splits open

AI disclosure: Codebase exploration and review via [Claude
Code](https://claude.com/claude-code)
This commit is contained in:
Mitchell Hashimoto
2026-03-10 08:59:51 -07:00
committed by GitHub
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.