Something went wrong. Try again.
Something went wrong. Try again.
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740const std = @import("std");const assert = @import("../quirks.zig").inlineAssert;const Allocator = std.mem.Allocator;const Action = @import("Binding.zig").Action;
/// A command is a named binding action that can be executed from/// something like a command palette.////// A command must be associated with a binding; all commands can be/// mapped to traditional `keybind` configurations. This restriction/// makes it so that there is nothing special about commands and likewise/// it makes it trivial and consistent to define custom commands.////// For apprt implementers: a command palette doesn't have to make use/// of all the fields here. We try to provide as much information as/// possible to make it easier to implement a command palette in the way/// that makes the most sense for the application.pub const Command = struct { action: Action, title: [:0]const u8, description: [:0]const u8 = "",
/// ghostty_command_s pub const C = extern struct { action_key: [*:0]const u8, action: [*:0]const u8, title: [*:0]const u8, description: [*:0]const u8, };
pub fn clone(self: *const Command, alloc: Allocator) Allocator.Error!Command { return .{ .action = try self.action.clone(alloc), .title = try alloc.dupeZ(u8, self.title), .description = try alloc.dupeZ(u8, self.description), }; }
pub fn equal(self: Command, other: Command) bool { if (self.action.hash() != other.action.hash()) return false; if (!std.mem.eql(u8, self.title, other.title)) return false; if (!std.mem.eql(u8, self.description, other.description)) return false; return true; }
/// Convert this command to a C struct at comptime. pub fn comptimeCval(self: Command) C { assert(@inComptime());
return .{ .action_key = @tagName(self.action), .action = std.fmt.comptimePrint("{f}", .{self.action}), .title = self.title, .description = self.description, }; }
/// Convert this command to a C struct at runtime. /// /// This shares memory with the original command. /// /// The action string is allocated using the provided allocator. You can /// free the slice directly if you need to but we recommend an arena /// for this. pub fn cval(self: Command, alloc: Allocator) Allocator.Error!C { var buf: std.Io.Writer.Allocating = .init(alloc); defer buf.deinit(); self.action.format(&buf.writer) catch return error.OutOfMemory; const action = try buf.toOwnedSliceSentinel(0);
return .{ .action_key = @tagName(self.action), .action = action.ptr, .title = self.title, .description = self.description, }; }
/// Implements a comparison function for std.mem.sortUnstable /// and similar functions. The sorting is defined by Ghostty /// to be what we prefer. If a caller wants some other sorting, /// they should do it themselves. pub fn lessThan(_: void, lhs: Command, rhs: Command) bool { return std.ascii.orderIgnoreCase(lhs.title, rhs.title) == .lt; }};
pub const defaults: []const Command = defaults: { @setEvalBranchQuota(100_000);
var count: usize = 0; for (@typeInfo(Action.Key).@"enum".fields) |field| { const action = @field(Action.Key, field.name); count += actionCommands(action).len; }
var result: [count]Command = undefined; var i: usize = 0; for (@typeInfo(Action.Key).@"enum".fields) |field| { const action = @field(Action.Key, field.name); const commands = actionCommands(action); for (commands) |cmd| { result[i] = cmd; i += 1; } }
std.mem.sortUnstable(Command, &result, {}, Command.lessThan);
assert(i == count); const final = result; break :defaults &final;};
/// Defaults in C-compatible form.pub const defaultsC: []const Command.C = defaults: { @setEvalBranchQuota(100_000); var result: [defaults.len]Command.C = undefined; for (defaults, 0..) |cmd, i| result[i] = cmd.comptimeCval(); const final = result; break :defaults &final;};
/// Returns the set of commands associated with this action key by/// default. Not all actions should have commands. As a general guideline,/// an action should have a command only if it is useful and reasonable/// to appear in a command palette.fn actionCommands(action: Action.Key) []const Command { // This is implemented as a function and switch rather than a // flat comptime const because we want to ensure we get a compiler // error when a new binding is added so that the contributor has // to consider whether that new binding should have commands or not. const result: []const Command = switch (action) { // Note: the use of `comptime` prefix on the return values // ensures that the data returned is all in the binary and // and not pointing to the stack.
.reset => comptime &.{.{ .action = .reset, .title = "Reset Terminal", .description = "Reset the terminal to a clean state.", }},
.copy_to_clipboard => comptime &.{ .{ .action = .{ .copy_to_clipboard = .mixed }, .title = "Copy to Clipboard", .description = "Copy the selected text to the clipboard in both plain and styled formats.", }, .{ .action = .{ .copy_to_clipboard = .plain }, .title = "Copy Selection as Plain Text to Clipboard", .description = "Copy the selected text as plain text to the clipboard.", }, .{ .action = .{ .copy_to_clipboard = .vt }, .title = "Copy Selection as ANSI Sequences to Clipboard", .description = "Copy the selected text as ANSI escape sequences to the clipboard.", }, .{ .action = .{ .copy_to_clipboard = .html }, .title = "Copy Selection as HTML to Clipboard", .description = "Copy the selected text as HTML to the clipboard.", } },
.copy_url_to_clipboard => comptime &.{.{ .action = .copy_url_to_clipboard, .title = "Copy URL to Clipboard", .description = "Copy the URL under the cursor to the clipboard.", }},
.copy_title_to_clipboard => comptime &.{.{ .action = .copy_title_to_clipboard, .title = "Copy Terminal Title to Clipboard", .description = "Copy the terminal title to the clipboard. If the terminal title is not set this has no effect.", }},
.paste_from_clipboard => comptime &.{.{ .action = .paste_from_clipboard, .title = "Paste from Clipboard", .description = "Paste the contents of the main clipboard.", }},
.paste_from_selection => comptime &.{.{ .action = .paste_from_selection, .title = "Paste from Selection", .description = "Paste the contents of the selection clipboard.", }},
.start_search => comptime &.{.{ .action = .start_search, .title = "Start Search", .description = "Start a search if one isn't already active.", }},
.search_selection => comptime &.{.{ .action = .search_selection, .title = "Search Selection", .description = "Start a search for the current text selection.", }},
.end_search => comptime &.{.{ .action = .end_search, .title = "End Search", .description = "End the current search if any and hide any GUI elements.", }},
.navigate_search => comptime &.{ .{ .action = .{ .navigate_search = .next }, .title = "Next Search Result", .description = "Navigate to the next search result, if any.", }, .{ .action = .{ .navigate_search = .previous }, .title = "Previous Search Result", .description = "Navigate to the previous search result, if any.", } },
.increase_font_size => comptime &.{.{ .action = .{ .increase_font_size = 1 }, .title = "Increase Font Size", .description = "Increase the font size by 1 point.", }},
.decrease_font_size => comptime &.{.{ .action = .{ .decrease_font_size = 1 }, .title = "Decrease Font Size", .description = "Decrease the font size by 1 point.", }},
.reset_font_size => comptime &.{.{ .action = .reset_font_size, .title = "Reset Font Size", .description = "Reset the font size to the default.", }},
.clear_screen => comptime &.{.{ .action = .clear_screen, .title = "Clear Screen", .description = "Clear the screen and scrollback.", }},
.select_all => comptime &.{.{ .action = .select_all, .title = "Select All", .description = "Select all text on the screen.", }},
.scroll_to_top => comptime &.{.{ .action = .scroll_to_top, .title = "Scroll to Top", .description = "Scroll to the top of the screen.", }},
.scroll_to_bottom => comptime &.{.{ .action = .scroll_to_bottom, .title = "Scroll to Bottom", .description = "Scroll to the bottom of the screen.", }},
.scroll_to_selection => comptime &.{.{ .action = .scroll_to_selection, .title = "Scroll to Selection", .description = "Scroll to the selected text.", }},
.scroll_page_up => comptime &.{.{ .action = .scroll_page_up, .title = "Scroll Page Up", .description = "Scroll the screen up by a page.", }},
.scroll_page_down => comptime &.{.{ .action = .scroll_page_down, .title = "Scroll Page Down", .description = "Scroll the screen down by a page.", }},
.write_screen_file => comptime &.{ .{ .action = .{ .write_screen_file = .copy }, .title = "Copy Screen to Temporary File and Copy Path", .description = "Copy the screen contents to a temporary file and copy the path to the clipboard.", }, .{ .action = .{ .write_screen_file = .paste }, .title = "Copy Screen to Temporary File and Paste Path", .description = "Copy the screen contents to a temporary file and paste the path to the file.", }, .{ .action = .{ .write_screen_file = .open }, .title = "Copy Screen to Temporary File and Open", .description = "Copy the screen contents to a temporary file and open it.", },
.{ .action = .{ .write_screen_file = .{ .action = .copy, .emit = .html, } }, .title = "Copy Screen as HTML to Temporary File and Copy Path", .description = "Copy the screen contents as HTML to a temporary file and copy the path to the clipboard.", }, .{ .action = .{ .write_screen_file = .{ .action = .paste, .emit = .html, } }, .title = "Copy Screen as HTML to Temporary File and Paste Path", .description = "Copy the screen contents as HTML to a temporary file and paste the path to the file.", }, .{ .action = .{ .write_screen_file = .{ .action = .open, .emit = .html, } }, .title = "Copy Screen as HTML to Temporary File and Open", .description = "Copy the screen contents as HTML to a temporary file and open it.", },
.{ .action = .{ .write_screen_file = .{ .action = .copy, .emit = .vt, } }, .title = "Copy Screen as ANSI Sequences to Temporary File and Copy Path", .description = "Copy the screen contents as ANSI escape sequences to a temporary file and copy the path to the clipboard.", }, .{ .action = .{ .write_screen_file = .{ .action = .paste, .emit = .vt, } }, .title = "Copy Screen as ANSI Sequences to Temporary File and Paste Path", .description = "Copy the screen contents as ANSI escape sequences to a temporary file and paste the path to the file.", }, .{ .action = .{ .write_screen_file = .{ .action = .open, .emit = .vt, } }, .title = "Copy Screen as ANSI Sequences to Temporary File and Open", .description = "Copy the screen contents as ANSI escape sequences to a temporary file and open it.", }, },
.write_selection_file => comptime &.{ .{ .action = .{ .write_selection_file = .copy }, .title = "Copy Selection to Temporary File and Copy Path", .description = "Copy the selection contents to a temporary file and copy the path to the clipboard.", }, .{ .action = .{ .write_selection_file = .paste }, .title = "Copy Selection to Temporary File and Paste Path", .description = "Copy the selection contents to a temporary file and paste the path to the file.", }, .{ .action = .{ .write_selection_file = .open }, .title = "Copy Selection to Temporary File and Open", .description = "Copy the selection contents to a temporary file and open it.", },
.{ .action = .{ .write_selection_file = .{ .action = .copy, .emit = .html, } }, .title = "Copy Selection as HTML to Temporary File and Copy Path", .description = "Copy the selection contents as HTML to a temporary file and copy the path to the clipboard.", }, .{ .action = .{ .write_selection_file = .{ .action = .paste, .emit = .html, } }, .title = "Copy Selection as HTML to Temporary File and Paste Path", .description = "Copy the selection contents as HTML to a temporary file and paste the path to the file.", }, .{ .action = .{ .write_selection_file = .{ .action = .open, .emit = .html, } }, .title = "Copy Selection as HTML to Temporary File and Open", .description = "Copy the selection contents as HTML to a temporary file and open it.", },
.{ .action = .{ .write_selection_file = .{ .action = .copy, .emit = .vt, } }, .title = "Copy Selection as ANSI Sequences to Temporary File and Copy Path", .description = "Copy the selection contents as ANSI escape sequences to a temporary file and copy the path to the clipboard.", }, .{ .action = .{ .write_selection_file = .{ .action = .paste, .emit = .vt, } }, .title = "Copy Selection as ANSI Sequences to Temporary File and Paste Path", .description = "Copy the selection contents as ANSI escape sequences to a temporary file and paste the path to the file.", }, .{ .action = .{ .write_selection_file = .{ .action = .open, .emit = .vt, } }, .title = "Copy Selection as ANSI Sequences to Temporary File and Open", .description = "Copy the selection contents as ANSI escape sequences to a temporary file and open it.", }, },
.new_window => comptime &.{.{ .action = .new_window, .title = "New Window", .description = "Open a new window.", }},
.new_tab => comptime &.{.{ .action = .new_tab, .title = "New Tab", .description = "Open a new tab.", }},
.move_tab => comptime &.{ .{ .action = .{ .move_tab = -1 }, .title = "Move Tab Left", .description = "Move the current tab to the left.", }, .{ .action = .{ .move_tab = 1 }, .title = "Move Tab Right", .description = "Move the current tab to the right.", }, },
.toggle_tab_overview => comptime &.{.{ .action = .toggle_tab_overview, .title = "Toggle Tab Overview", .description = "Toggle the tab overview.", }},
.prompt_surface_title => comptime &.{.{ .action = .prompt_surface_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…", .description = "Prompt for a new title for the current tab.", }},
.new_split => comptime &.{ .{ .action = .{ .new_split = .left }, .title = "Split Left", .description = "Split the terminal to the left.", }, .{ .action = .{ .new_split = .right }, .title = "Split Right", .description = "Split the terminal to the right.", }, .{ .action = .{ .new_split = .up }, .title = "Split Up", .description = "Split the terminal up.", }, .{ .action = .{ .new_split = .down }, .title = "Split Down", .description = "Split the terminal down.", }, },
.goto_split => comptime &.{ .{ .action = .{ .goto_split = .previous }, .title = "Focus Split: Previous", .description = "Focus the previous split, if any.", }, .{ .action = .{ .goto_split = .next }, .title = "Focus Split: Next", .description = "Focus the next split, if any.", }, .{ .action = .{ .goto_split = .left }, .title = "Focus Split: Left", .description = "Focus the split to the left, if it exists.", }, .{ .action = .{ .goto_split = .right }, .title = "Focus Split: Right", .description = "Focus the split to the right, if it exists.", }, .{ .action = .{ .goto_split = .up }, .title = "Focus Split: Up", .description = "Focus the split above, if it exists.", }, .{ .action = .{ .goto_split = .down }, .title = "Focus Split: Down", .description = "Focus the split below, if it exists.", }, },
.goto_window => comptime &.{ .{ .action = .{ .goto_window = .previous }, .title = "Focus Window: Previous", .description = "Focus the previous window, if any.", }, .{ .action = .{ .goto_window = .next }, .title = "Focus Window: Next", .description = "Focus the next window, if any.", }, },
.toggle_split_zoom => comptime &.{.{ .action = .toggle_split_zoom, .title = "Toggle Split Zoom", .description = "Toggle the zoom state of the current split.", }},
.toggle_readonly => comptime &.{.{ .action = .toggle_readonly, .title = "Toggle Read-Only Mode", .description = "Toggle read-only mode for the current surface.", }},
.equalize_splits => comptime &.{.{ .action = .equalize_splits, .title = "Equalize Splits", .description = "Equalize the size of all splits.", }},
.reset_window_size => comptime &.{.{ .action = .reset_window_size, .title = "Reset Window Size", .description = "Reset the window size to the default.", }},
.inspector => comptime &.{.{ .action = .{ .inspector = .toggle }, .title = "Toggle Inspector", .description = "Toggle the inspector.", }},
.show_gtk_inspector => comptime &.{.{ .action = .show_gtk_inspector, .title = "Show the GTK Inspector", .description = "Show the GTK inspector.", }},
.show_on_screen_keyboard => comptime &.{.{ .action = .show_on_screen_keyboard, .title = "Show On-Screen Keyboard", .description = "Show the on-screen keyboard if present.", }},
.open_config => comptime &.{.{ .action = .open_config, .title = "Open Config", .description = "Open the config file.", }},
.reload_config => comptime &.{.{ .action = .reload_config, .title = "Reload Config", .description = "Reload the config file.", }},
.close_surface => comptime &.{.{ .action = .close_surface, .title = "Close Terminal", .description = "Close the current terminal.", }},
.close_tab => comptime &.{ .{ .action = .{ .close_tab = .this }, .title = "Close Tab", .description = "Close the current tab.", }, .{ .action = .{ .close_tab = .other }, .title = "Close Other Tabs", .description = "Close all tabs in this window except the current one.", }, .{ .action = .{ .close_tab = .right }, .title = "Close Tabs to the Right", .description = "Close all tabs to the right of the current one.", }, },
.close_window => comptime &.{.{ .action = .close_window, .title = "Close Window", .description = "Close the current window.", }},
.close_all_windows => comptime &.{.{ .action = .close_all_windows, .title = "Close All Windows", .description = "Close all windows.", }},
.toggle_maximize => comptime &.{.{ .action = .toggle_maximize, .title = "Toggle Maximize", .description = "Toggle the maximized state of the current window.", }},
.toggle_fullscreen => comptime &.{.{ .action = .toggle_fullscreen, .title = "Toggle Fullscreen", .description = "Toggle the fullscreen state of the current window.", }},
.toggle_window_decorations => comptime &.{.{ .action = .toggle_window_decorations, .title = "Toggle Window Decorations", .description = "Toggle the window decorations.", }},
.toggle_window_float_on_top => comptime &.{.{ .action = .toggle_window_float_on_top, .title = "Toggle Float on Top", .description = "Toggle the float on top state of the current window.", }},
.toggle_secure_input => comptime &.{.{ .action = .toggle_secure_input, .title = "Toggle Secure Input", .description = "Toggle secure input mode.", }},
.toggle_mouse_reporting => comptime &.{.{ .action = .toggle_mouse_reporting, .title = "Toggle Mouse Reporting", .description = "Toggle whether mouse events are reported to terminal applications.", }},
.toggle_background_opacity => comptime &.{.{ .action = .toggle_background_opacity, .title = "Toggle Background Opacity", .description = "Toggle the background opacity of a window that started transparent.", }},
.check_for_updates => comptime &.{.{ .action = .check_for_updates, .title = "Check for Updates", .description = "Check for updates to the application.", }},
.undo => comptime &.{.{ .action = .undo, .title = "Undo", .description = "Undo the last action.", }},
.redo => comptime &.{.{ .action = .redo, .title = "Redo", .description = "Redo the last undone action.", }},
.quit => comptime &.{.{ .action = .quit, .title = "Quit", .description = "Quit the application.", }},
.text => comptime &.{.{ .action = .{ .text = "👻" }, .title = "Ghostty", .description = "Put a little Ghostty in your terminal.", }},
// No commands because they're parameterized and there // aren't obvious values users would use. It is possible that // these may have commands in the future if there are very // common values that users tend to use. .csi, .esc, .cursor_key, .set_font_size, .set_surface_title, .set_tab_title, .search, .scroll_to_row, .scroll_page_fractional, .scroll_page_lines, .adjust_selection, .jump_to_prompt, .write_scrollback_file, .goto_tab, .resize_split, .activate_key_table, .activate_key_table_once, .deactivate_key_table, .deactivate_all_key_tables, .end_key_sequence, .crash, => comptime &.{},
// No commands because I'm not sure they make sense in a command // palette context. .toggle_command_palette, .toggle_quick_terminal, .toggle_visibility, .previous_tab, .next_tab, .last_tab, => comptime &.{},
// No commands for obvious reasons .ignore, .unbind, => comptime &.{}, };
// All generated commands should have the same action as the // action passed in. for (result) |cmd| assert(cmd.action == action);
return result;}
test "command defaults" { // This just ensures that defaults is analyzed and works. const testing = std.testing; try testing.expect(defaults.len > 0); try testing.expectEqual(defaults.len, defaultsC.len);}