Skip to content

Commit fb480e2

Browse files
committed
Replace the alert dialog for tab title editing with popover
1 parent aeccbd2 commit fb480e2

File tree

1 file changed

+83
-34
lines changed

1 file changed

+83
-34
lines changed

macos/Sources/Ghostty/SurfaceView_AppKit.swift

+83-34
Original file line numberDiff line numberDiff line change
@@ -387,40 +387,45 @@ extension Ghostty {
387387

388388
/// Set the title by prompting the user.
389389
func promptTitle() {
390-
// Create an alert dialog
391-
let alert = NSAlert()
392-
alert.messageText = "Change Terminal Title"
393-
alert.informativeText = "Leave blank to restore the default."
394-
alert.alertStyle = .informational
395-
396-
// Add a text field to the alert
397-
let textField = NSTextField(frame: NSRect(x: 0, y: 0, width: 250, height: 24))
398-
textField.stringValue = title
399-
alert.accessoryView = textField
400-
401-
// Add buttons
402-
alert.addButton(withTitle: "OK")
403-
alert.addButton(withTitle: "Cancel")
404-
405-
let response = alert.runModal()
406-
407-
// Check if the user clicked "OK"
408-
if response == .alertFirstButtonReturn {
409-
// Get the input text
410-
let newTitle = textField.stringValue
411-
412-
if newTitle.isEmpty {
413-
// Empty means that user wants the title to be set automatically
414-
// We also need to reload the config for the "title" property to be
415-
// used again by this tab.
416-
let prevTitle = titleFromTerminal ?? "👻"
417-
titleFromTerminal = nil
418-
setTitle(prevTitle)
419-
} else {
420-
// Set the title and prevent it from being changed automatically
421-
titleFromTerminal = title
422-
title = newTitle
390+
// Create a popover
391+
let hostingController = NSHostingController(rootView: TabTitleEditPopover(
392+
currentTitle: title,
393+
onComplete: { [weak self] newTitle in
394+
if newTitle.isEmpty {
395+
// Empty means that user wants the title to be set automatically
396+
let prevTitle = self?.titleFromTerminal ?? "👻"
397+
self?.titleFromTerminal = nil
398+
self?.setTitle(prevTitle)
399+
} else {
400+
// Set the title and prevent it from being changed automatically
401+
self?.titleFromTerminal = self?.title
402+
self?.title = newTitle
403+
}
423404
}
405+
))
406+
407+
let popover = NSPopover()
408+
popover.contentViewController = hostingController
409+
popover.behavior = .transient
410+
411+
// Show the popover below the current tab title
412+
if let window = self.window as? TerminalWindow,
413+
let toolbar = window.toolbar as? TerminalToolbar,
414+
let titleItem = toolbar.items.first(where: { $0.itemIdentifier == .titleText }),
415+
let titleView = titleItem.view {
416+
popover.show(
417+
relativeTo: titleView.bounds,
418+
of: titleView,
419+
preferredEdge: .maxY
420+
)
421+
} else if let window = self.window,
422+
let titlebarView = window.contentView?.superview?.firstDescendant(withClassName: "NSTitlebarView"),
423+
let titleView = titlebarView.firstDescendant(withClassName: "NSTextField") {
424+
popover.show(
425+
relativeTo: titleView.bounds,
426+
of: titleView,
427+
preferredEdge: .maxY
428+
)
424429
}
425430
}
426431

@@ -1240,7 +1245,7 @@ extension Ghostty {
12401245
AppDelegate.logger.warning("action failed action=\(action)")
12411246
}
12421247
}
1243-
1248+
12441249
@IBAction func changeTitle(_ sender: Any) {
12451250
promptTitle()
12461251
}
@@ -1607,3 +1612,47 @@ extension Ghostty.SurfaceView {
16071612
return false
16081613
}
16091614
}
1615+
1616+
struct TabTitleEditPopover: View {
1617+
@Environment(\.dismiss) private var dismiss
1618+
@State private var newTitle: String
1619+
let currentTitle: String
1620+
let onComplete: (String) -> Void
1621+
1622+
init(currentTitle: String, onComplete: @escaping (String) -> Void) {
1623+
self.currentTitle = currentTitle
1624+
self._newTitle = State(initialValue: currentTitle)
1625+
self.onComplete = onComplete
1626+
}
1627+
1628+
var body: some View {
1629+
VStack(alignment: .leading, spacing: 8) {
1630+
TextField("Enter title (leave empty for default)", text: $newTitle)
1631+
.textFieldStyle(RoundedBorderTextFieldStyle())
1632+
.onSubmit(submit)
1633+
1634+
HStack(spacing: 8) {
1635+
Button(action: { dismiss() }) {
1636+
Text("Cancel")
1637+
.frame(maxWidth: .infinity)
1638+
}
1639+
.buttonStyle(.bordered)
1640+
.keyboardShortcut(.cancelAction)
1641+
1642+
Button(action: { submit() }) {
1643+
Text("OK")
1644+
.frame(maxWidth: .infinity)
1645+
}
1646+
.buttonStyle(.borderedProminent)
1647+
.keyboardShortcut(.defaultAction)
1648+
}
1649+
.fixedSize(horizontal: false, vertical: true)
1650+
}
1651+
.padding()
1652+
}
1653+
1654+
private func submit() {
1655+
onComplete(newTitle)
1656+
dismiss()
1657+
}
1658+
}

0 commit comments

Comments
 (0)