Replace the alert dialog for tab title editing with popover

This commit is contained in:
Bryan Lee
2025-02-16 05:36:39 +08:00
parent 9a5bc65034
commit 5c9d929d5d

View File

@ -387,40 +387,45 @@ extension Ghostty {
/// Set the title by prompting the user.
func promptTitle() {
// Create an alert dialog
let alert = NSAlert()
alert.messageText = "Change Terminal Title"
alert.informativeText = "Leave blank to restore the default."
alert.alertStyle = .informational
// Add a text field to the alert
let textField = NSTextField(frame: NSRect(x: 0, y: 0, width: 250, height: 24))
textField.stringValue = title
alert.accessoryView = textField
// Add buttons
alert.addButton(withTitle: "OK")
alert.addButton(withTitle: "Cancel")
let response = alert.runModal()
// Check if the user clicked "OK"
if response == .alertFirstButtonReturn {
// Get the input text
let newTitle = textField.stringValue
if newTitle.isEmpty {
// Empty means that user wants the title to be set automatically
// We also need to reload the config for the "title" property to be
// used again by this tab.
let prevTitle = titleFromTerminal ?? "👻"
titleFromTerminal = nil
setTitle(prevTitle)
} else {
// Set the title and prevent it from being changed automatically
titleFromTerminal = title
title = newTitle
// Create a popover
let hostingController = NSHostingController(rootView: TabTitleEditPopover(
currentTitle: title,
onComplete: { [weak self] newTitle in
if newTitle.isEmpty {
// Empty means that user wants the title to be set automatically
let prevTitle = self?.titleFromTerminal ?? "👻"
self?.titleFromTerminal = nil
self?.setTitle(prevTitle)
} else {
// Set the title and prevent it from being changed automatically
self?.titleFromTerminal = self?.title
self?.title = newTitle
}
}
))
let popover = NSPopover()
popover.contentViewController = hostingController
popover.behavior = .transient
// Show the popover below the current tab title
if let window = self.window as? TerminalWindow,
let toolbar = window.toolbar as? TerminalToolbar,
let titleItem = toolbar.items.first(where: { $0.itemIdentifier == .titleText }),
let titleView = titleItem.view {
popover.show(
relativeTo: titleView.bounds,
of: titleView,
preferredEdge: .maxY
)
} else if let window = self.window,
let titlebarView = window.contentView?.superview?.firstDescendant(withClassName: "NSTitlebarView"),
let titleView = titlebarView.firstDescendant(withClassName: "NSTextField") {
popover.show(
relativeTo: titleView.bounds,
of: titleView,
preferredEdge: .maxY
)
}
}
@ -1240,7 +1245,7 @@ extension Ghostty {
AppDelegate.logger.warning("action failed action=\(action)")
}
}
@IBAction func changeTitle(_ sender: Any) {
promptTitle()
}
@ -1607,3 +1612,47 @@ extension Ghostty.SurfaceView {
return false
}
}
struct TabTitleEditPopover: View {
@Environment(\.dismiss) private var dismiss
@State private var newTitle: String
let currentTitle: String
let onComplete: (String) -> Void
init(currentTitle: String, onComplete: @escaping (String) -> Void) {
self.currentTitle = currentTitle
self._newTitle = State(initialValue: currentTitle)
self.onComplete = onComplete
}
var body: some View {
VStack(alignment: .leading, spacing: 8) {
TextField("Enter title (leave empty for default)", text: $newTitle)
.textFieldStyle(RoundedBorderTextFieldStyle())
.onSubmit(submit)
HStack(spacing: 8) {
Button(action: { dismiss() }) {
Text("Cancel")
.frame(maxWidth: .infinity)
}
.buttonStyle(.bordered)
.keyboardShortcut(.cancelAction)
Button(action: { submit() }) {
Text("OK")
.frame(maxWidth: .infinity)
}
.buttonStyle(.borderedProminent)
.keyboardShortcut(.defaultAction)
}
.fixedSize(horizontal: false, vertical: true)
}
.padding()
}
private func submit() {
onComplete(newTitle)
dismiss()
}
}