macOS: Change Tab Title (#9879)
This adds the ability to change a _tab_ title. The previous functionality was tied to a specific _surface_. A tab title will stick to the current tab regardless of active splits and so on. This follows the nomenclature that macOS terminal app does which is "title vs terminal title" (although we explicitly use "tab" in various places, I may remove that in the future). **This is macOS only. GTK is tracked here: #9880**. I did macOS only because thats the machine I'm on. It'll be trivial to add this to GTK, too. ## Demo https://github.com/user-attachments/assets/d9446785-d919-4212-8553-db50c56c8c2f (The option is also in the main menu, the context menu, and the command palette) ## AI Disclosure This PR was done fully with Amp, I didn't write a single line of code at the time of writing this PR description. I reviewed everything though and fully understand it all. Its a mimic more or less of the prompt surface title work (although we did unify some stuff like the apprt action).pull/9885/head
commit
cba82e976c
|
|
@ -584,6 +584,12 @@ typedef struct {
|
||||||
const char* title;
|
const char* title;
|
||||||
} ghostty_action_set_title_s;
|
} ghostty_action_set_title_s;
|
||||||
|
|
||||||
|
// apprt.action.PromptTitle
|
||||||
|
typedef enum {
|
||||||
|
GHOSTTY_PROMPT_TITLE_SURFACE,
|
||||||
|
GHOSTTY_PROMPT_TITLE_TAB,
|
||||||
|
} ghostty_action_prompt_title_e;
|
||||||
|
|
||||||
// apprt.action.Pwd.C
|
// apprt.action.Pwd.C
|
||||||
typedef struct {
|
typedef struct {
|
||||||
const char* pwd;
|
const char* pwd;
|
||||||
|
|
@ -831,7 +837,7 @@ typedef enum {
|
||||||
GHOSTTY_ACTION_END_SEARCH,
|
GHOSTTY_ACTION_END_SEARCH,
|
||||||
GHOSTTY_ACTION_SEARCH_TOTAL,
|
GHOSTTY_ACTION_SEARCH_TOTAL,
|
||||||
GHOSTTY_ACTION_SEARCH_SELECTED,
|
GHOSTTY_ACTION_SEARCH_SELECTED,
|
||||||
} ghostty_action_tag_e;
|
} ghostty_action_tag_e;
|
||||||
|
|
||||||
typedef union {
|
typedef union {
|
||||||
ghostty_action_split_direction_e new_split;
|
ghostty_action_split_direction_e new_split;
|
||||||
|
|
@ -847,6 +853,7 @@ typedef union {
|
||||||
ghostty_action_inspector_e inspector;
|
ghostty_action_inspector_e inspector;
|
||||||
ghostty_action_desktop_notification_s desktop_notification;
|
ghostty_action_desktop_notification_s desktop_notification;
|
||||||
ghostty_action_set_title_s set_title;
|
ghostty_action_set_title_s set_title;
|
||||||
|
ghostty_action_prompt_title_e prompt_title;
|
||||||
ghostty_action_pwd_s pwd;
|
ghostty_action_pwd_s pwd;
|
||||||
ghostty_action_mouse_shape_e mouse_shape;
|
ghostty_action_mouse_shape_e mouse_shape;
|
||||||
ghostty_action_mouse_visibility_e mouse_visibility;
|
ghostty_action_mouse_visibility_e mouse_visibility;
|
||||||
|
|
|
||||||
|
|
@ -68,6 +68,7 @@ class AppDelegate: NSObject,
|
||||||
@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 menuChangeTitle: NSMenuItem?
|
||||||
|
@IBOutlet private var menuChangeTabTitle: NSMenuItem?
|
||||||
@IBOutlet private var menuQuickTerminal: NSMenuItem?
|
@IBOutlet private var menuQuickTerminal: NSMenuItem?
|
||||||
@IBOutlet private var menuTerminalInspector: NSMenuItem?
|
@IBOutlet private var menuTerminalInspector: NSMenuItem?
|
||||||
@IBOutlet private var menuCommandPalette: NSMenuItem?
|
@IBOutlet private var menuCommandPalette: NSMenuItem?
|
||||||
|
|
@ -541,7 +542,7 @@ class AppDelegate: NSObject,
|
||||||
self.menuDecreaseFontSize?.setImageIfDesired(systemSymbolName: "textformat.size.smaller")
|
self.menuDecreaseFontSize?.setImageIfDesired(systemSymbolName: "textformat.size.smaller")
|
||||||
self.menuCommandPalette?.setImageIfDesired(systemSymbolName: "filemenu.and.selection")
|
self.menuCommandPalette?.setImageIfDesired(systemSymbolName: "filemenu.and.selection")
|
||||||
self.menuQuickTerminal?.setImageIfDesired(systemSymbolName: "apple.terminal")
|
self.menuQuickTerminal?.setImageIfDesired(systemSymbolName: "apple.terminal")
|
||||||
self.menuChangeTitle?.setImageIfDesired(systemSymbolName: "pencil.line")
|
self.menuChangeTabTitle?.setImageIfDesired(systemSymbolName: "pencil.line")
|
||||||
self.menuTerminalInspector?.setImageIfDesired(systemSymbolName: "scope")
|
self.menuTerminalInspector?.setImageIfDesired(systemSymbolName: "scope")
|
||||||
self.menuToggleFullScreen?.setImageIfDesired(systemSymbolName: "square.arrowtriangle.4.outward")
|
self.menuToggleFullScreen?.setImageIfDesired(systemSymbolName: "square.arrowtriangle.4.outward")
|
||||||
self.menuToggleVisibility?.setImageIfDesired(systemSymbolName: "eye")
|
self.menuToggleVisibility?.setImageIfDesired(systemSymbolName: "eye")
|
||||||
|
|
@ -609,6 +610,7 @@ class AppDelegate: NSObject,
|
||||||
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: "prompt_surface_title", menuItem: self.menuChangeTitle)
|
syncMenuShortcut(config, action: "prompt_surface_title", menuItem: self.menuChangeTitle)
|
||||||
|
syncMenuShortcut(config, action: "prompt_tab_title", menuItem: self.menuChangeTabTitle)
|
||||||
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: "toggle_window_float_on_top", menuItem: self.menuFloatOnTop)
|
syncMenuShortcut(config, action: "toggle_window_float_on_top", menuItem: self.menuFloatOnTop)
|
||||||
|
|
|
||||||
|
|
@ -16,6 +16,7 @@
|
||||||
<connections>
|
<connections>
|
||||||
<outlet property="menuAbout" destination="5kV-Vb-QxS" id="Y5y-UO-NK6"/>
|
<outlet property="menuAbout" destination="5kV-Vb-QxS" id="Y5y-UO-NK6"/>
|
||||||
<outlet property="menuBringAllToFront" destination="LE2-aR-0XJ" id="AP9-oK-60V"/>
|
<outlet property="menuBringAllToFront" destination="LE2-aR-0XJ" id="AP9-oK-60V"/>
|
||||||
|
<outlet property="menuChangeTabTitle" destination="iac-lh-Cl7" id="tId-v0-a3E"/>
|
||||||
<outlet property="menuChangeTitle" destination="24I-xg-qIq" id="kg6-kT-jNL"/>
|
<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"/>
|
||||||
|
|
@ -315,7 +316,13 @@
|
||||||
<action selector="toggleCommandPalette:" target="-1" id="FcT-XD-gM1"/>
|
<action selector="toggleCommandPalette:" target="-1" id="FcT-XD-gM1"/>
|
||||||
</connections>
|
</connections>
|
||||||
</menuItem>
|
</menuItem>
|
||||||
<menuItem title="Change Title..." id="24I-xg-qIq">
|
<menuItem title="Change Tab Title..." id="iac-lh-Cl7">
|
||||||
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
|
<connections>
|
||||||
|
<action selector="changeTabTitle:" target="-1" id="Jhl-9P-bMj"/>
|
||||||
|
</connections>
|
||||||
|
</menuItem>
|
||||||
|
<menuItem title="Change Terminal Title..." id="24I-xg-qIq">
|
||||||
<modifierMask key="keyEquivalentModifierMask"/>
|
<modifierMask key="keyEquivalentModifierMask"/>
|
||||||
<connections>
|
<connections>
|
||||||
<action selector="changeTitle:" target="-1" id="XuL-QB-Q9l"/>
|
<action selector="changeTitle:" target="-1" id="XuL-QB-Q9l"/>
|
||||||
|
|
|
||||||
|
|
@ -81,6 +81,15 @@ class BaseTerminalController: NSWindowController,
|
||||||
/// The cancellables related to our focused surface.
|
/// The cancellables related to our focused surface.
|
||||||
private var focusedSurfaceCancellables: Set<AnyCancellable> = []
|
private var focusedSurfaceCancellables: Set<AnyCancellable> = []
|
||||||
|
|
||||||
|
/// An override title for the tab/window set by the user via prompt_tab_title.
|
||||||
|
/// When set, this takes precedence over the computed title from the terminal.
|
||||||
|
var titleOverride: String? = nil {
|
||||||
|
didSet { applyTitleToWindow() }
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The last computed title from the focused surface (without the override).
|
||||||
|
private var lastComputedTitle: String = "👻"
|
||||||
|
|
||||||
/// The time that undo/redo operations that contain running ptys are valid for.
|
/// The time that undo/redo operations that contain running ptys are valid for.
|
||||||
var undoExpiration: Duration {
|
var undoExpiration: Duration {
|
||||||
ghostty.config.undoTimeout
|
ghostty.config.undoTimeout
|
||||||
|
|
@ -325,6 +334,37 @@ class BaseTerminalController: NSWindowController,
|
||||||
self.alert = alert
|
self.alert = alert
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Prompt the user to change the tab/window title.
|
||||||
|
func promptTabTitle() {
|
||||||
|
guard let window else { return }
|
||||||
|
|
||||||
|
let alert = NSAlert()
|
||||||
|
alert.messageText = "Change Tab Title"
|
||||||
|
alert.informativeText = "Leave blank to restore the default."
|
||||||
|
alert.alertStyle = .informational
|
||||||
|
|
||||||
|
let textField = NSTextField(frame: NSRect(x: 0, y: 0, width: 250, height: 24))
|
||||||
|
textField.stringValue = titleOverride ?? window.title
|
||||||
|
alert.accessoryView = textField
|
||||||
|
|
||||||
|
alert.addButton(withTitle: "OK")
|
||||||
|
alert.addButton(withTitle: "Cancel")
|
||||||
|
|
||||||
|
alert.window.initialFirstResponder = textField
|
||||||
|
|
||||||
|
alert.beginSheetModal(for: window) { [weak self] response in
|
||||||
|
guard let self else { return }
|
||||||
|
guard response == .alertFirstButtonReturn else { return }
|
||||||
|
|
||||||
|
let newTitle = textField.stringValue
|
||||||
|
if newTitle.isEmpty {
|
||||||
|
self.titleOverride = nil
|
||||||
|
} else {
|
||||||
|
self.titleOverride = newTitle
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/// Close a surface from a view.
|
/// Close a surface from a view.
|
||||||
func closeSurface(
|
func closeSurface(
|
||||||
_ view: Ghostty.SurfaceView,
|
_ view: Ghostty.SurfaceView,
|
||||||
|
|
@ -718,10 +758,13 @@ class BaseTerminalController: NSWindowController,
|
||||||
}
|
}
|
||||||
|
|
||||||
private func titleDidChange(to: String) {
|
private func titleDidChange(to: String) {
|
||||||
|
lastComputedTitle = to
|
||||||
|
applyTitleToWindow()
|
||||||
|
}
|
||||||
|
|
||||||
|
private func applyTitleToWindow() {
|
||||||
guard let window else { return }
|
guard let window else { return }
|
||||||
|
window.title = titleOverride ?? lastComputedTitle
|
||||||
// Set the main window title
|
|
||||||
window.title = to
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func pwdDidChange(to: URL?) {
|
func pwdDidChange(to: URL?) {
|
||||||
|
|
@ -1017,6 +1060,10 @@ class BaseTerminalController: NSWindowController,
|
||||||
window.performClose(sender)
|
window.performClose(sender)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@IBAction func changeTabTitle(_ sender: Any) {
|
||||||
|
promptTabTitle()
|
||||||
|
}
|
||||||
|
|
||||||
@IBAction func splitRight(_ sender: Any) {
|
@IBAction func splitRight(_ sender: Any) {
|
||||||
guard let surface = focusedSurface?.surface else { return }
|
guard let surface = focusedSurface?.surface else { return }
|
||||||
ghostty.split(surface: surface, direction: GHOSTTY_SPLIT_DIRECTION_RIGHT)
|
ghostty.split(surface: surface, direction: GHOSTTY_SPLIT_DIRECTION_RIGHT)
|
||||||
|
|
|
||||||
|
|
@ -4,18 +4,20 @@ import Cocoa
|
||||||
class TerminalRestorableState: Codable {
|
class TerminalRestorableState: Codable {
|
||||||
static let selfKey = "state"
|
static let selfKey = "state"
|
||||||
static let versionKey = "version"
|
static let versionKey = "version"
|
||||||
static let version: Int = 6
|
static let version: Int = 7
|
||||||
|
|
||||||
let focusedSurface: String?
|
let focusedSurface: String?
|
||||||
let surfaceTree: SplitTree<Ghostty.SurfaceView>
|
let surfaceTree: SplitTree<Ghostty.SurfaceView>
|
||||||
let effectiveFullscreenMode: FullscreenMode?
|
let effectiveFullscreenMode: FullscreenMode?
|
||||||
let tabColor: TerminalTabColor
|
let tabColor: TerminalTabColor
|
||||||
|
let titleOverride: String?
|
||||||
|
|
||||||
init(from controller: TerminalController) {
|
init(from controller: TerminalController) {
|
||||||
self.focusedSurface = controller.focusedSurface?.id.uuidString
|
self.focusedSurface = controller.focusedSurface?.id.uuidString
|
||||||
self.surfaceTree = controller.surfaceTree
|
self.surfaceTree = controller.surfaceTree
|
||||||
self.effectiveFullscreenMode = controller.fullscreenStyle?.fullscreenMode
|
self.effectiveFullscreenMode = controller.fullscreenStyle?.fullscreenMode
|
||||||
self.tabColor = (controller.window as? TerminalWindow)?.tabColor ?? .none
|
self.tabColor = (controller.window as? TerminalWindow)?.tabColor ?? .none
|
||||||
|
self.titleOverride = controller.titleOverride
|
||||||
}
|
}
|
||||||
|
|
||||||
init?(coder aDecoder: NSCoder) {
|
init?(coder aDecoder: NSCoder) {
|
||||||
|
|
@ -34,6 +36,7 @@ class TerminalRestorableState: Codable {
|
||||||
self.focusedSurface = v.value.focusedSurface
|
self.focusedSurface = v.value.focusedSurface
|
||||||
self.effectiveFullscreenMode = v.value.effectiveFullscreenMode
|
self.effectiveFullscreenMode = v.value.effectiveFullscreenMode
|
||||||
self.tabColor = v.value.tabColor
|
self.tabColor = v.value.tabColor
|
||||||
|
self.titleOverride = v.value.titleOverride
|
||||||
}
|
}
|
||||||
|
|
||||||
func encode(with coder: NSCoder) {
|
func encode(with coder: NSCoder) {
|
||||||
|
|
@ -100,6 +103,9 @@ class TerminalWindowRestoration: NSObject, NSWindowRestoration {
|
||||||
// Restore our tab color
|
// Restore our tab color
|
||||||
(window as? TerminalWindow)?.tabColor = state.tabColor
|
(window as? TerminalWindow)?.tabColor = state.tabColor
|
||||||
|
|
||||||
|
// Restore the tab title override
|
||||||
|
c.titleOverride = state.titleOverride
|
||||||
|
|
||||||
// Setup our restored state on the controller
|
// Setup our restored state on the controller
|
||||||
// Find the focused surface in surfaceTree
|
// Find the focused surface in surfaceTree
|
||||||
if let focusedStr = state.focusedSurface {
|
if let focusedStr = state.focusedSurface {
|
||||||
|
|
|
||||||
|
|
@ -668,6 +668,7 @@ private struct TabColorIndicatorView: View {
|
||||||
|
|
||||||
extension TerminalWindow {
|
extension TerminalWindow {
|
||||||
private static let closeTabsOnRightMenuItemIdentifier = NSUserInterfaceItemIdentifier("com.mitchellh.ghostty.closeTabsOnTheRightMenuItem")
|
private static let closeTabsOnRightMenuItemIdentifier = NSUserInterfaceItemIdentifier("com.mitchellh.ghostty.closeTabsOnTheRightMenuItem")
|
||||||
|
private static let changeTitleMenuItemIdentifier = NSUserInterfaceItemIdentifier("com.mitchellh.ghostty.changeTitleMenuItem")
|
||||||
private static let tabColorSeparatorIdentifier = NSUserInterfaceItemIdentifier("com.mitchellh.ghostty.tabColorSeparator")
|
private static let tabColorSeparatorIdentifier = NSUserInterfaceItemIdentifier("com.mitchellh.ghostty.tabColorSeparator")
|
||||||
|
|
||||||
private static let tabColorPaletteIdentifier = NSUserInterfaceItemIdentifier("com.mitchellh.ghostty.tabColorPalette")
|
private static let tabColorPaletteIdentifier = NSUserInterfaceItemIdentifier("com.mitchellh.ghostty.tabColorPalette")
|
||||||
|
|
@ -701,7 +702,7 @@ extension TerminalWindow {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
appendTabColorSection(to: menu, target: targetController)
|
appendTabModifierSection(to: menu, target: targetController)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func isTabContextMenu(_ menu: NSMenu) -> Bool {
|
private func isTabContextMenu(_ menu: NSMenu) -> Bool {
|
||||||
|
|
@ -719,9 +720,10 @@ extension TerminalWindow {
|
||||||
return !selectorNames.isDisjoint(with: tabContextSelectors)
|
return !selectorNames.isDisjoint(with: tabContextSelectors)
|
||||||
}
|
}
|
||||||
|
|
||||||
private func appendTabColorSection(to menu: NSMenu, target: TerminalController?) {
|
private func appendTabModifierSection(to menu: NSMenu, target: TerminalController?) {
|
||||||
menu.removeItems(withIdentifiers: [
|
menu.removeItems(withIdentifiers: [
|
||||||
Self.tabColorSeparatorIdentifier,
|
Self.tabColorSeparatorIdentifier,
|
||||||
|
Self.changeTitleMenuItemIdentifier,
|
||||||
Self.tabColorPaletteIdentifier
|
Self.tabColorPaletteIdentifier
|
||||||
])
|
])
|
||||||
|
|
||||||
|
|
@ -729,6 +731,13 @@ extension TerminalWindow {
|
||||||
separator.identifier = Self.tabColorSeparatorIdentifier
|
separator.identifier = Self.tabColorSeparatorIdentifier
|
||||||
menu.addItem(separator)
|
menu.addItem(separator)
|
||||||
|
|
||||||
|
// Change Title...
|
||||||
|
let changeTitleItem = NSMenuItem(title: "Change Title...", action: #selector(BaseTerminalController.changeTabTitle(_:)), keyEquivalent: "")
|
||||||
|
changeTitleItem.identifier = Self.changeTitleMenuItemIdentifier
|
||||||
|
changeTitleItem.target = target
|
||||||
|
changeTitleItem.setImageIfDesired(systemSymbolName: "pencil.line")
|
||||||
|
menu.addItem(changeTitleItem)
|
||||||
|
|
||||||
let paletteItem = NSMenuItem()
|
let paletteItem = NSMenuItem()
|
||||||
paletteItem.identifier = Self.tabColorPaletteIdentifier
|
paletteItem.identifier = Self.tabColorPaletteIdentifier
|
||||||
paletteItem.view = makeTabColorPaletteView(
|
paletteItem.view = makeTabColorPaletteView(
|
||||||
|
|
|
||||||
|
|
@ -127,6 +127,20 @@ extension Ghostty.Action {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enum PromptTitle {
|
||||||
|
case surface
|
||||||
|
case tab
|
||||||
|
|
||||||
|
init(_ c: ghostty_action_prompt_title_e) {
|
||||||
|
switch c {
|
||||||
|
case GHOSTTY_PROMPT_TITLE_TAB:
|
||||||
|
self = .tab
|
||||||
|
default:
|
||||||
|
self = .surface
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Putting the initializer in an extension preserves the automatic one.
|
// Putting the initializer in an extension preserves the automatic one.
|
||||||
|
|
|
||||||
|
|
@ -523,7 +523,7 @@ extension Ghostty {
|
||||||
setTitle(app, target: target, v: action.action.set_title)
|
setTitle(app, target: target, v: action.action.set_title)
|
||||||
|
|
||||||
case GHOSTTY_ACTION_PROMPT_TITLE:
|
case GHOSTTY_ACTION_PROMPT_TITLE:
|
||||||
return promptTitle(app, target: target)
|
return promptTitle(app, target: target, v: action.action.prompt_title)
|
||||||
|
|
||||||
case GHOSTTY_ACTION_PWD:
|
case GHOSTTY_ACTION_PWD:
|
||||||
pwdChanged(app, target: target, v: action.action.pwd)
|
pwdChanged(app, target: target, v: action.action.pwd)
|
||||||
|
|
@ -1350,22 +1350,50 @@ extension Ghostty {
|
||||||
|
|
||||||
private static func promptTitle(
|
private static func promptTitle(
|
||||||
_ app: ghostty_app_t,
|
_ app: ghostty_app_t,
|
||||||
target: ghostty_target_s) -> Bool {
|
target: ghostty_target_s,
|
||||||
switch (target.tag) {
|
v: ghostty_action_prompt_title_e) -> Bool {
|
||||||
case GHOSTTY_TARGET_APP:
|
let promptTitle = Action.PromptTitle(v)
|
||||||
Ghostty.logger.warning("set title prompt does nothing with an app target")
|
switch promptTitle {
|
||||||
return false
|
case .surface:
|
||||||
|
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:
|
case GHOSTTY_TARGET_SURFACE:
|
||||||
guard let surface = target.target.surface else { return false }
|
guard let surface = target.target.surface else { return false }
|
||||||
guard let surfaceView = self.surfaceView(from: surface) else { return false }
|
guard let surfaceView = self.surfaceView(from: surface) else { return false }
|
||||||
surfaceView.promptTitle()
|
surfaceView.promptTitle()
|
||||||
|
return true
|
||||||
|
|
||||||
default:
|
default:
|
||||||
assertionFailure()
|
assertionFailure()
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
case .tab:
|
||||||
|
switch (target.tag) {
|
||||||
|
case GHOSTTY_TARGET_APP:
|
||||||
|
guard let window = NSApp.mainWindow ?? NSApp.keyWindow,
|
||||||
|
let controller = window.windowController as? BaseTerminalController
|
||||||
|
else { return false }
|
||||||
|
controller.promptTabTitle()
|
||||||
|
return true
|
||||||
|
|
||||||
|
case GHOSTTY_TARGET_SURFACE:
|
||||||
|
guard let surface = target.target.surface else { return false }
|
||||||
|
guard let surfaceView = self.surfaceView(from: surface) else { return false }
|
||||||
|
guard let window = surfaceView.window,
|
||||||
|
let controller = window.windowController as? BaseTerminalController
|
||||||
|
else { return false }
|
||||||
|
controller.promptTabTitle()
|
||||||
|
return true
|
||||||
|
|
||||||
|
default:
|
||||||
|
assertionFailure()
|
||||||
|
return false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return true
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func pwdChanged(
|
private static func pwdChanged(
|
||||||
|
|
|
||||||
|
|
@ -1417,8 +1417,9 @@ extension Ghostty {
|
||||||
item = menu.addItem(withTitle: "Toggle Terminal Inspector", action: #selector(toggleTerminalInspector(_:)), keyEquivalent: "")
|
item = menu.addItem(withTitle: "Toggle Terminal Inspector", action: #selector(toggleTerminalInspector(_:)), keyEquivalent: "")
|
||||||
item.setImageIfDesired(systemSymbolName: "scope")
|
item.setImageIfDesired(systemSymbolName: "scope")
|
||||||
menu.addItem(.separator())
|
menu.addItem(.separator())
|
||||||
item = menu.addItem(withTitle: "Change Title...", action: #selector(changeTitle(_:)), keyEquivalent: "")
|
item = menu.addItem(withTitle: "Change Tab Title...", action: #selector(BaseTerminalController.changeTabTitle(_:)), keyEquivalent: "")
|
||||||
item.setImageIfDesired(systemSymbolName: "pencil.line")
|
item.setImageIfDesired(systemSymbolName: "pencil.line")
|
||||||
|
item = menu.addItem(withTitle: "Change Terminal Title...", action: #selector(changeTitle(_:)), keyEquivalent: "")
|
||||||
|
|
||||||
return menu
|
return menu
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -5183,7 +5183,13 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
|
||||||
.prompt_surface_title => return try self.rt_app.performAction(
|
.prompt_surface_title => return try self.rt_app.performAction(
|
||||||
.{ .surface = self },
|
.{ .surface = self },
|
||||||
.prompt_title,
|
.prompt_title,
|
||||||
{},
|
.surface,
|
||||||
|
),
|
||||||
|
|
||||||
|
.prompt_tab_title => return try self.rt_app.performAction(
|
||||||
|
.{ .surface = self },
|
||||||
|
.prompt_title,
|
||||||
|
.tab,
|
||||||
),
|
),
|
||||||
|
|
||||||
.clear_screen => {
|
.clear_screen => {
|
||||||
|
|
|
||||||
|
|
@ -189,8 +189,9 @@ pub const Action = union(Key) {
|
||||||
set_title: SetTitle,
|
set_title: SetTitle,
|
||||||
|
|
||||||
/// Set the title of the target to a prompted value. It is up to
|
/// Set the title of the target to a prompted value. It is up to
|
||||||
/// the apprt to prompt.
|
/// the apprt to prompt. The value specifies whether to prompt for the
|
||||||
prompt_title,
|
/// surface title or the tab title.
|
||||||
|
prompt_title: PromptTitle,
|
||||||
|
|
||||||
/// The current working directory has changed for the target terminal.
|
/// The current working directory has changed for the target terminal.
|
||||||
pwd: Pwd,
|
pwd: Pwd,
|
||||||
|
|
@ -536,6 +537,12 @@ pub const MouseVisibility = enum(c_int) {
|
||||||
hidden,
|
hidden,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/// Whether to prompt for the surface title or tab title.
|
||||||
|
pub const PromptTitle = enum(c_int) {
|
||||||
|
surface,
|
||||||
|
tab,
|
||||||
|
};
|
||||||
|
|
||||||
pub const MouseOverLink = struct {
|
pub const MouseOverLink = struct {
|
||||||
url: [:0]const u8,
|
url: [:0]const u8,
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -693,7 +693,7 @@ pub const Application = extern struct {
|
||||||
|
|
||||||
.progress_report => return Action.progressReport(target, value),
|
.progress_report => return Action.progressReport(target, value),
|
||||||
|
|
||||||
.prompt_title => return Action.promptTitle(target),
|
.prompt_title => return Action.promptTitle(target, value),
|
||||||
|
|
||||||
.quit => self.quit(),
|
.quit => self.quit(),
|
||||||
|
|
||||||
|
|
@ -2250,12 +2250,18 @@ const Action = struct {
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn promptTitle(target: apprt.Target) bool {
|
pub fn promptTitle(target: apprt.Target, value: apprt.action.PromptTitle) bool {
|
||||||
switch (target) {
|
switch (value) {
|
||||||
.app => return false,
|
.surface => switch (target) {
|
||||||
.surface => |v| {
|
.app => return false,
|
||||||
v.rt_surface.surface.promptTitle();
|
.surface => |v| {
|
||||||
return true;
|
v.rt_surface.surface.promptTitle();
|
||||||
|
return true;
|
||||||
|
},
|
||||||
|
},
|
||||||
|
.tab => {
|
||||||
|
// GTK does not yet support tab title prompting
|
||||||
|
return false;
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -519,6 +519,11 @@ pub const Action = union(enum) {
|
||||||
/// version can be found by running `ghostty +version`.
|
/// version can be found by running `ghostty +version`.
|
||||||
prompt_surface_title,
|
prompt_surface_title,
|
||||||
|
|
||||||
|
/// Change the title of the current tab/window via a pop-up prompt. The
|
||||||
|
/// title set via this prompt overrides any title set by the terminal
|
||||||
|
/// and persists across focus changes within the tab.
|
||||||
|
prompt_tab_title,
|
||||||
|
|
||||||
/// Create a new split in the specified direction.
|
/// Create a new split in the specified direction.
|
||||||
///
|
///
|
||||||
/// Valid arguments:
|
/// Valid arguments:
|
||||||
|
|
@ -1191,6 +1196,7 @@ pub const Action = union(enum) {
|
||||||
.reset_font_size,
|
.reset_font_size,
|
||||||
.set_font_size,
|
.set_font_size,
|
||||||
.prompt_surface_title,
|
.prompt_surface_title,
|
||||||
|
.prompt_tab_title,
|
||||||
.clear_screen,
|
.clear_screen,
|
||||||
.select_all,
|
.select_all,
|
||||||
.scroll_to_top,
|
.scroll_to_top,
|
||||||
|
|
|
||||||
|
|
@ -413,10 +413,16 @@ fn actionCommands(action: Action.Key) []const Command {
|
||||||
|
|
||||||
.prompt_surface_title => comptime &.{.{
|
.prompt_surface_title => comptime &.{.{
|
||||||
.action = .prompt_surface_title,
|
.action = .prompt_surface_title,
|
||||||
.title = "Change Title...",
|
.title = "Change Terminal Title...",
|
||||||
.description = "Prompt for a new title for the current terminal.",
|
.description = "Prompt for a new title for the current terminal.",
|
||||||
}},
|
}},
|
||||||
|
|
||||||
|
.prompt_tab_title => comptime &.{.{
|
||||||
|
.action = .prompt_tab_title,
|
||||||
|
.title = "Change Tab Title...",
|
||||||
|
.description = "Prompt for a new title for the current tab.",
|
||||||
|
}},
|
||||||
|
|
||||||
.new_split => comptime &.{
|
.new_split => comptime &.{
|
||||||
.{
|
.{
|
||||||
.action = .{ .new_split = .left },
|
.action = .{ .new_split = .left },
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue