mirror of
https://github.com/ghostty-org/ghostty.git
synced 2025-05-02 14:28:37 +03:00
Add tab title rename feature to macos
This commit is contained in:

committed by
Mitchell Hashimoto

parent
228b4dbd60
commit
a581955b9b
@ -586,6 +586,7 @@ typedef enum {
|
|||||||
GHOSTTY_ACTION_RENDER_INSPECTOR,
|
GHOSTTY_ACTION_RENDER_INSPECTOR,
|
||||||
GHOSTTY_ACTION_DESKTOP_NOTIFICATION,
|
GHOSTTY_ACTION_DESKTOP_NOTIFICATION,
|
||||||
GHOSTTY_ACTION_SET_TITLE,
|
GHOSTTY_ACTION_SET_TITLE,
|
||||||
|
GHOSTTY_ACTION_PROMPT_TITLE,
|
||||||
GHOSTTY_ACTION_PWD,
|
GHOSTTY_ACTION_PWD,
|
||||||
GHOSTTY_ACTION_MOUSE_SHAPE,
|
GHOSTTY_ACTION_MOUSE_SHAPE,
|
||||||
GHOSTTY_ACTION_MOUSE_VISIBILITY,
|
GHOSTTY_ACTION_MOUSE_VISIBILITY,
|
||||||
|
@ -52,6 +52,7 @@ class AppDelegate: NSObject,
|
|||||||
@IBOutlet private var menuIncreaseFontSize: NSMenuItem?
|
@IBOutlet private var menuIncreaseFontSize: NSMenuItem?
|
||||||
@IBOutlet private var menuDecreaseFontSize: NSMenuItem?
|
@IBOutlet private var menuDecreaseFontSize: NSMenuItem?
|
||||||
@IBOutlet private var menuResetFontSize: NSMenuItem?
|
@IBOutlet private var menuResetFontSize: NSMenuItem?
|
||||||
|
@IBOutlet private var menuChangeTitle: NSMenuItem?
|
||||||
@IBOutlet private var menuQuickTerminal: NSMenuItem?
|
@IBOutlet private var menuQuickTerminal: NSMenuItem?
|
||||||
@IBOutlet private var menuTerminalInspector: 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: "increase_font_size:1", menuItem: self.menuIncreaseFontSize)
|
||||||
syncMenuShortcut(config, action: "decrease_font_size:1", menuItem: self.menuDecreaseFontSize)
|
syncMenuShortcut(config, action: "decrease_font_size:1", menuItem: self.menuDecreaseFontSize)
|
||||||
syncMenuShortcut(config, action: "reset_font_size", menuItem: self.menuResetFontSize)
|
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_quick_terminal", menuItem: self.menuQuickTerminal)
|
||||||
syncMenuShortcut(config, action: "toggle_visibility", menuItem: self.menuToggleVisibility)
|
syncMenuShortcut(config, action: "toggle_visibility", menuItem: self.menuToggleVisibility)
|
||||||
syncMenuShortcut(config, action: "inspector:toggle", menuItem: self.menuTerminalInspector)
|
syncMenuShortcut(config, action: "inspector:toggle", menuItem: self.menuTerminalInspector)
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||||
<customObject id="bbz-4X-AYv" userLabel="AppDelegate" customClass="AppDelegate" customModule="Ghostty" customModuleProvider="target">
|
<customObject id="bbz-4X-AYv" userLabel="AppDelegate" customClass="AppDelegate" customModule="Ghostty" customModuleProvider="target">
|
||||||
<connections>
|
<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="menuCheckForUpdates" destination="GEA-5y-yzH" id="0nV-Tf-nJQ"/>
|
||||||
<outlet property="menuClose" destination="DVo-aG-piG" id="R3t-0C-aSU"/>
|
<outlet property="menuClose" destination="DVo-aG-piG" id="R3t-0C-aSU"/>
|
||||||
<outlet property="menuCloseAllWindows" destination="yKr-Vi-Yqw" id="Zet-Ir-zbm"/>
|
<outlet property="menuCloseAllWindows" destination="yKr-Vi-Yqw" id="Zet-Ir-zbm"/>
|
||||||
@ -232,6 +233,13 @@
|
|||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem isSeparatorItem="YES" id="L3L-I8-sqk"/>
|
<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">
|
<menuItem title="Quick Terminal" id="1pv-LF-NBJ">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
|
@ -484,6 +484,9 @@ extension Ghostty {
|
|||||||
case GHOSTTY_ACTION_SET_TITLE:
|
case GHOSTTY_ACTION_SET_TITLE:
|
||||||
setTitle(app, target: target, v: action.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:
|
case GHOSTTY_ACTION_PWD:
|
||||||
pwdChanged(app, target: target, v: action.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(
|
private static func pwdChanged(
|
||||||
_ app: ghostty_app_t,
|
_ app: ghostty_app_t,
|
||||||
target: ghostty_target_s,
|
target: ghostty_target_s,
|
||||||
|
@ -124,6 +124,11 @@ extension Ghostty {
|
|||||||
// A timer to fallback to ghost emoji if no title is set within the grace period
|
// A timer to fallback to ghost emoji if no title is set within the grace period
|
||||||
private var titleFallbackTimer: Timer?
|
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)
|
/// Event monitor (see individual events for why)
|
||||||
private var eventMonitor: Any? = nil
|
private var eventMonitor: Any? = nil
|
||||||
|
|
||||||
@ -380,6 +385,45 @@ extension Ghostty {
|
|||||||
NSCursor.setHiddenUntilMouseMoves(!visible)
|
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) {
|
func setTitle(_ title: String) {
|
||||||
// This fixes an issue where very quick changes to the title could
|
// This fixes an issue where very quick changes to the title could
|
||||||
// cause an unpleasant flickering. We set a timer so that we can
|
// cause an unpleasant flickering. We set a timer so that we can
|
||||||
@ -390,6 +434,11 @@ extension Ghostty {
|
|||||||
withTimeInterval: 0.075,
|
withTimeInterval: 0.075,
|
||||||
repeats: false
|
repeats: false
|
||||||
) { [weak self] _ in
|
) { [weak self] _ in
|
||||||
|
// Set the title if it wasn't manually set.
|
||||||
|
guard self?.titleFromTerminal == nil else {
|
||||||
|
self?.titleFromTerminal = title
|
||||||
|
return
|
||||||
|
}
|
||||||
self?.title = title
|
self?.title = title
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -1117,6 +1166,8 @@ extension Ghostty {
|
|||||||
menu.addItem(.separator())
|
menu.addItem(.separator())
|
||||||
menu.addItem(withTitle: "Reset Terminal", action: #selector(resetTerminal(_:)), keyEquivalent: "")
|
menu.addItem(withTitle: "Reset Terminal", action: #selector(resetTerminal(_:)), keyEquivalent: "")
|
||||||
menu.addItem(withTitle: "Toggle Terminal Inspector", action: #selector(toggleTerminalInspector(_:)), 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
|
return menu
|
||||||
}
|
}
|
||||||
@ -1189,6 +1240,10 @@ extension Ghostty {
|
|||||||
AppDelegate.logger.warning("action failed action=\(action)")
|
AppDelegate.logger.warning("action failed action=\(action)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@IBAction func changeTitle(_ sender: Any) {
|
||||||
|
promptTitle()
|
||||||
|
}
|
||||||
|
|
||||||
/// Show a user notification and associate it with this surface
|
/// Show a user notification and associate it with this surface
|
||||||
func showUserNotification(title: String, body: String) {
|
func showUserNotification(title: String, body: String) {
|
||||||
|
@ -4024,6 +4024,12 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
|||||||
try self.setFontSize(size);
|
try self.setFontSize(size);
|
||||||
},
|
},
|
||||||
|
|
||||||
|
.prompt_surface_title => return try self.rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.prompt_title,
|
||||||
|
{},
|
||||||
|
),
|
||||||
|
|
||||||
.clear_screen => {
|
.clear_screen => {
|
||||||
// This is a duplicate of some of the logic in termio.clearScreen
|
// 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
|
// but we need to do this here so we can know the answer before
|
||||||
|
@ -158,9 +158,13 @@ pub const Action = union(Key) {
|
|||||||
/// Show a desktop notification.
|
/// Show a desktop notification.
|
||||||
desktop_notification: DesktopNotification,
|
desktop_notification: DesktopNotification,
|
||||||
|
|
||||||
/// Set the title of the target.
|
/// Set the title of the target to the requested value.
|
||||||
set_title: SetTitle,
|
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.
|
/// The current working directory has changed for the target terminal.
|
||||||
pwd: Pwd,
|
pwd: Pwd,
|
||||||
|
|
||||||
@ -254,6 +258,7 @@ pub const Action = union(Key) {
|
|||||||
render_inspector,
|
render_inspector,
|
||||||
desktop_notification,
|
desktop_notification,
|
||||||
set_title,
|
set_title,
|
||||||
|
prompt_title,
|
||||||
pwd,
|
pwd,
|
||||||
mouse_shape,
|
mouse_shape,
|
||||||
mouse_visibility,
|
mouse_visibility,
|
||||||
|
@ -239,6 +239,7 @@ pub const App = struct {
|
|||||||
.pwd,
|
.pwd,
|
||||||
.config_change,
|
.config_change,
|
||||||
.toggle_maximize,
|
.toggle_maximize,
|
||||||
|
.prompt_title,
|
||||||
=> {
|
=> {
|
||||||
log.info("unimplemented action={}", .{action});
|
log.info("unimplemented action={}", .{action});
|
||||||
return false;
|
return false;
|
||||||
|
@ -560,6 +560,7 @@ pub fn performAction(
|
|||||||
.render_inspector,
|
.render_inspector,
|
||||||
.renderer_health,
|
.renderer_health,
|
||||||
.color_change,
|
.color_change,
|
||||||
|
.prompt_title,
|
||||||
=> {
|
=> {
|
||||||
log.warn("unimplemented action={}", .{action});
|
log.warn("unimplemented action={}", .{action});
|
||||||
return false;
|
return false;
|
||||||
|
@ -348,6 +348,10 @@ pub const Action = union(enum) {
|
|||||||
/// This only works with libadwaita enabled currently.
|
/// This only works with libadwaita enabled currently.
|
||||||
toggle_tab_overview: void,
|
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.
|
/// Create a new split in the given direction.
|
||||||
///
|
///
|
||||||
/// Arguments:
|
/// Arguments:
|
||||||
@ -748,6 +752,7 @@ pub const Action = union(enum) {
|
|||||||
.increase_font_size,
|
.increase_font_size,
|
||||||
.decrease_font_size,
|
.decrease_font_size,
|
||||||
.reset_font_size,
|
.reset_font_size,
|
||||||
|
.prompt_surface_title,
|
||||||
.clear_screen,
|
.clear_screen,
|
||||||
.select_all,
|
.select_all,
|
||||||
.scroll_to_top,
|
.scroll_to_top,
|
||||||
|
Reference in New Issue
Block a user