aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/input/command.zig
diff options
context:
space:
mode:
Diffstat (limited to 'vendor/libghostty-vt/src/input/command.zig')
-rw-r--r--vendor/libghostty-vt/src/input/command.zig739
1 files changed, 739 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/input/command.zig b/vendor/libghostty-vt/src/input/command.zig
new file mode 100644
index 00000000..ac048eec
--- /dev/null
+++ b/vendor/libghostty-vt/src/input/command.zig
@@ -0,0 +1,739 @@
+const 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);
+}