Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions include/ghostty.h
Original file line number Diff line number Diff line change
Expand Up @@ -630,6 +630,7 @@ typedef struct {
typedef enum {
GHOSTTY_PROMPT_TITLE_SURFACE,
GHOSTTY_PROMPT_TITLE_TAB,
GHOSTTY_PROMPT_TITLE_WINDOW,
} ghostty_action_prompt_title_e;

// apprt.action.Pwd.C
Expand Down
31 changes: 31 additions & 0 deletions macos/Sources/Features/Terminal/BaseTerminalController.swift
Original file line number Diff line number Diff line change
Expand Up @@ -378,6 +378,37 @@ class BaseTerminalController: NSWindowController,
}
}

/// Prompt the user to change the window title.
func promptWindowTitle() {
guard let window else { return }

let alert = NSAlert()
alert.messageText = "Change Window 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.
func closeSurface(
_ view: Ghostty.SurfaceView,
Expand Down
3 changes: 3 additions & 0 deletions macos/Sources/Ghostty/Ghostty.Action.swift
Original file line number Diff line number Diff line change
Expand Up @@ -131,11 +131,14 @@ extension Ghostty.Action {
enum PromptTitle {
case surface
case tab
case window

init(_ c: ghostty_action_prompt_title_e) {
switch c {
case GHOSTTY_PROMPT_TITLE_TAB:
self = .tab
case GHOSTTY_PROMPT_TITLE_WINDOW:
self = .window
default:
self = .surface
}
Expand Down
23 changes: 23 additions & 0 deletions macos/Sources/Ghostty/Ghostty.App.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1572,6 +1572,29 @@ extension Ghostty {
assertionFailure()
return false
}

case .window:
switch (target.tag) {
case GHOSTTY_TARGET_APP:
guard let window = NSApp.mainWindow ?? NSApp.keyWindow,
let controller = window.windowController as? BaseTerminalController
else { return false }
controller.promptWindowTitle()
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.promptWindowTitle()
return true

default:
assertionFailure()
return false
}
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/Surface.zig
Original file line number Diff line number Diff line change
Expand Up @@ -5478,6 +5478,12 @@ pub fn performBindingAction(self: *Surface, action: input.Binding.Action) !bool
.tab,
),

.prompt_window_title => return try self.rt_app.performAction(
.{ .surface = self },
.prompt_title,
.window,
),

.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
Expand Down
3 changes: 2 additions & 1 deletion src/apprt/action.zig
Original file line number Diff line number Diff line change
Expand Up @@ -575,10 +575,11 @@ pub const MouseVisibility = enum(c_int) {
hidden,
};

/// Whether to prompt for the surface title or tab title.
/// Whether to prompt for the surface title, tab title, or window title.
pub const PromptTitle = enum(c_int) {
surface,
tab,
window,
};

pub const MouseOverLink = struct {
Expand Down
4 changes: 4 additions & 0 deletions src/apprt/gtk/class/application.zig
Original file line number Diff line number Diff line change
Expand Up @@ -2382,6 +2382,10 @@ const Action = struct {
},
}
},
.window => {
// GTK does not yet support window title prompting
return false;
},
}
}

Expand Down
6 changes: 6 additions & 0 deletions src/input/Binding.zig
Original file line number Diff line number Diff line change
Expand Up @@ -580,6 +580,11 @@ pub const Action = union(enum) {
/// and persists across focus changes within the tab.
prompt_tab_title,

/// Change the title of the current window via a pop-up prompt. The
/// title set via this prompt overrides any title set by the terminal
/// and persists across tab changes within the window.
prompt_window_title,

/// Create a new split in the specified direction.
///
/// Valid arguments:
Expand Down Expand Up @@ -1327,6 +1332,7 @@ pub const Action = union(enum) {
.set_font_size,
.prompt_surface_title,
.prompt_tab_title,
.prompt_window_title,
.clear_screen,
.select_all,
.scroll_to_top,
Expand Down
10 changes: 8 additions & 2 deletions src/input/command.zig
Original file line number Diff line number Diff line change
Expand Up @@ -440,16 +440,22 @@ fn actionCommands(action: Action.Key) []const Command {

.prompt_surface_title => comptime &.{.{
.action = .prompt_surface_title,
.title = "Change Terminal Title...",
.title = "Change Terminal Title…",
.description = "Prompt for a new title for the current terminal.",
}},

.prompt_tab_title => comptime &.{.{
.action = .prompt_tab_title,
.title = "Change Tab Title...",
.title = "Change Tab Title…",
.description = "Prompt for a new title for the current tab.",
}},

.prompt_window_title => comptime &.{.{
.action = .prompt_window_title,
.title = "Change Window Title…",
.description = "Prompt for a new title for the current window.",
}},

.new_split => comptime &.{
.{
.action = .{ .new_split = .left },
Expand Down