Add tab title rename feature to macos

This commit is contained in:
Aswin M Prabhu
2025-01-01 01:01:42 +05:30
committed by Mitchell Hashimoto
parent 228b4dbd60
commit a581955b9b
10 changed files with 108 additions and 1 deletions

View File

@ -586,6 +586,7 @@ typedef enum {
GHOSTTY_ACTION_RENDER_INSPECTOR,
GHOSTTY_ACTION_DESKTOP_NOTIFICATION,
GHOSTTY_ACTION_SET_TITLE,
GHOSTTY_ACTION_PROMPT_TITLE,
GHOSTTY_ACTION_PWD,
GHOSTTY_ACTION_MOUSE_SHAPE,
GHOSTTY_ACTION_MOUSE_VISIBILITY,

View File

@ -52,6 +52,7 @@ class AppDelegate: NSObject,
@IBOutlet private var menuIncreaseFontSize: NSMenuItem?
@IBOutlet private var menuDecreaseFontSize: NSMenuItem?
@IBOutlet private var menuResetFontSize: NSMenuItem?
@IBOutlet private var menuChangeTitle: NSMenuItem?
@IBOutlet private var menuQuickTerminal: NSMenuItem?
@IBOutlet private var menuTerminalInspector: NSMenuItem?
@ -384,6 +385,7 @@ class AppDelegate: NSObject,
syncMenuShortcut(config, action: "increase_font_size:1", menuItem: self.menuIncreaseFontSize)
syncMenuShortcut(config, action: "decrease_font_size:1", menuItem: self.menuDecreaseFontSize)
syncMenuShortcut(config, action: "reset_font_size", menuItem: self.menuResetFontSize)
syncMenuShortcut(config, action: "change_title_prompt", menuItem: self.menuChangeTitle)
syncMenuShortcut(config, action: "toggle_quick_terminal", menuItem: self.menuQuickTerminal)
syncMenuShortcut(config, action: "toggle_visibility", menuItem: self.menuToggleVisibility)
syncMenuShortcut(config, action: "inspector:toggle", menuItem: self.menuTerminalInspector)

View File

@ -14,6 +14,7 @@
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
<customObject id="bbz-4X-AYv" userLabel="AppDelegate" customClass="AppDelegate" customModule="Ghostty" customModuleProvider="target">
<connections>
<outlet property="menuChangeTitle" destination="24I-xg-qIq" id="kg6-kT-jNL"/>
<outlet property="menuCheckForUpdates" destination="GEA-5y-yzH" id="0nV-Tf-nJQ"/>
<outlet property="menuClose" destination="DVo-aG-piG" id="R3t-0C-aSU"/>
<outlet property="menuCloseAllWindows" destination="yKr-Vi-Yqw" id="Zet-Ir-zbm"/>
@ -232,6 +233,13 @@
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="L3L-I8-sqk"/>
<menuItem title="Change Title..." id="24I-xg-qIq">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>
<action selector="changeTitle:" target="-1" id="XuL-QB-Q9l"/>
</connections>
</menuItem>
<menuItem isSeparatorItem="YES" id="Vkj-tP-dMZ"/>
<menuItem title="Quick Terminal" id="1pv-LF-NBJ">
<modifierMask key="keyEquivalentModifierMask"/>
<connections>

View File

@ -484,6 +484,9 @@ extension Ghostty {
case GHOSTTY_ACTION_SET_TITLE:
setTitle(app, target: target, v: action.action.set_title)
case GHOSTTY_ACTION_PROMPT_TITLE:
return promptTitle(app, target: target)
case GHOSTTY_ACTION_PWD:
pwdChanged(app, target: target, v: action.action.pwd)
@ -1007,6 +1010,26 @@ extension Ghostty {
}
}
private static func promptTitle(
_ app: ghostty_app_t,
target: ghostty_target_s) -> Bool {
switch (target.tag) {
case GHOSTTY_TARGET_APP:
Ghostty.logger.warning("set title prompt does nothing with an app target")
return false
case GHOSTTY_TARGET_SURFACE:
guard let surface = target.target.surface else { return false }
guard let surfaceView = self.surfaceView(from: surface) else { return false }
surfaceView.promptTitle()
default:
assertionFailure()
}
return true
}
private static func pwdChanged(
_ app: ghostty_app_t,
target: ghostty_target_s,

View File

@ -124,6 +124,11 @@ extension Ghostty {
// A timer to fallback to ghost emoji if no title is set within the grace period
private var titleFallbackTimer: Timer?
// This is the title from the terminal. This is nil if we're currently using
// the terminal title as the main title property. If the title is set manually
// by the user, this is set to the prior value (which may be empty, but non-nil).
private var titleFromTerminal: String?
/// Event monitor (see individual events for why)
private var eventMonitor: Any? = nil
@ -380,6 +385,45 @@ extension Ghostty {
NSCursor.setHiddenUntilMouseMoves(!visible)
}
/// 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
}
}
}
func setTitle(_ title: String) {
// This fixes an issue where very quick changes to the title could
// cause an unpleasant flickering. We set a timer so that we can
@ -390,6 +434,11 @@ extension Ghostty {
withTimeInterval: 0.075,
repeats: false
) { [weak self] _ in
// Set the title if it wasn't manually set.
guard self?.titleFromTerminal == nil else {
self?.titleFromTerminal = title
return
}
self?.title = title
}
}
@ -1117,6 +1166,8 @@ extension Ghostty {
menu.addItem(.separator())
menu.addItem(withTitle: "Reset Terminal", action: #selector(resetTerminal(_:)), keyEquivalent: "")
menu.addItem(withTitle: "Toggle Terminal Inspector", action: #selector(toggleTerminalInspector(_:)), keyEquivalent: "")
menu.addItem(.separator())
menu.addItem(withTitle: "Change Title...", action: #selector(changeTitle(_:)), keyEquivalent: "")
return menu
}
@ -1189,6 +1240,10 @@ extension Ghostty {
AppDelegate.logger.warning("action failed action=\(action)")
}
}
@IBAction func changeTitle(_ sender: Any) {
promptTitle()
}
/// Show a user notification and associate it with this surface
func showUserNotification(title: String, body: String) {

View File

@ -4024,6 +4024,12 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
try self.setFontSize(size);
},
.prompt_surface_title => return try self.rt_app.performAction(
.{ .surface = self },
.prompt_title,
{},
),
.clear_screen => {
// This is a duplicate of some of the logic in termio.clearScreen
// but we need to do this here so we can know the answer before

View File

@ -158,9 +158,13 @@ pub const Action = union(Key) {
/// Show a desktop notification.
desktop_notification: DesktopNotification,
/// Set the title of the target.
/// Set the title of the target to the requested value.
set_title: SetTitle,
/// Set the title of the target to a prompted value. It is up to
/// the apprt to prompt.
prompt_title,
/// The current working directory has changed for the target terminal.
pwd: Pwd,
@ -254,6 +258,7 @@ pub const Action = union(Key) {
render_inspector,
desktop_notification,
set_title,
prompt_title,
pwd,
mouse_shape,
mouse_visibility,

View File

@ -239,6 +239,7 @@ pub const App = struct {
.pwd,
.config_change,
.toggle_maximize,
.prompt_title,
=> {
log.info("unimplemented action={}", .{action});
return false;

View File

@ -560,6 +560,7 @@ pub fn performAction(
.render_inspector,
.renderer_health,
.color_change,
.prompt_title,
=> {
log.warn("unimplemented action={}", .{action});
return false;

View File

@ -348,6 +348,10 @@ pub const Action = union(enum) {
/// This only works with libadwaita enabled currently.
toggle_tab_overview: void,
/// Change the title of the current focused surface via a prompt.
/// This only works on macOS currently.
prompt_surface_title: void,
/// Create a new split in the given direction.
///
/// Arguments:
@ -748,6 +752,7 @@ pub const Action = union(enum) {
.increase_font_size,
.decrease_font_size,
.reset_font_size,
.prompt_surface_title,
.clear_screen,
.select_all,
.scroll_to_top,