diff --git a/src/Surface.zig b/src/Surface.zig index 64a995265..44e5bbafd 100644 --- a/src/Surface.zig +++ b/src/Surface.zig @@ -632,6 +632,7 @@ pub fn init( .env_override = config.env, .shell_integration = config.@"shell-integration", .shell_integration_features = config.@"shell-integration-features", + .cursor_blink = config.@"cursor-style-blink", .working_directory = config.@"working-directory", .resources_dir = global_state.resources_dir.host(), .term = config.term, diff --git a/src/config/Config.zig b/src/config/Config.zig index 8ca64efe9..02d3f0d04 100644 --- a/src/config/Config.zig +++ b/src/config/Config.zig @@ -2710,7 +2710,7 @@ keybind: Keybinds = .{}, /// /// Available features: /// -/// * `cursor` - Set the cursor to a blinking bar at the prompt. +/// * `cursor` - Set the cursor to a bar at the prompt. /// /// * `sudo` - Set sudo wrapper to preserve terminfo. /// diff --git a/src/shell-integration/bash/ghostty.bash b/src/shell-integration/bash/ghostty.bash index 65a49a190..e49742e71 100644 --- a/src/shell-integration/bash/ghostty.bash +++ b/src/shell-integration/bash/ghostty.bash @@ -213,7 +213,10 @@ function __ghostty_precmd() { # Cursor if [[ "$GHOSTTY_SHELL_FEATURES" == *"cursor"* ]]; then - [[ "$PS1" != *'\[\e[5 q\]'* ]] && PS1=$PS1'\[\e[5 q\]' # input + builtin local cursor=5 # blinking bar + [[ "$GHOSTTY_SHELL_FEATURES" == *"cursor:steady"* ]] && cursor=6 # steady bar + + [[ "$PS1" != *"\[\e[${cursor} q\]"* ]] && PS1=$PS1"\[\e[${cursor} q\]" [[ "$PS0" != *'\[\e[0 q\]'* ]] && PS0=$PS0'\[\e[0 q\]' # reset fi diff --git a/src/shell-integration/elvish/lib/ghostty-integration.elv b/src/shell-integration/elvish/lib/ghostty-integration.elv index a7c8bfc0c..776aab676 100644 --- a/src/shell-integration/elvish/lib/ghostty-integration.elv +++ b/src/shell-integration/elvish/lib/ghostty-integration.elv @@ -154,11 +154,16 @@ set edit:after-readline = (conj $edit:after-readline $mark-output-start~) set edit:after-command = (conj $edit:after-command $mark-output-end~) - if (has-value $features cursor) { - fn beam { printf "\e[5 q" } - fn block { printf "\e[0 q" } + if (str:contains $E:GHOSTTY_SHELL_FEATURES "cursor") { + var cursor = "5" # blinking bar + if (has-value $features cursor:steady) { + set cursor = "6" # steady bar + } + + fn beam { printf "\e["$cursor" q" } + fn reset { printf "\e[0 q" } set edit:before-readline = (conj $edit:before-readline $beam~) - set edit:after-readline = (conj $edit:after-readline {|_| block }) + set edit:after-readline = (conj $edit:after-readline {|_| reset }) } if (and (has-value $features path) (has-env GHOSTTY_BIN_DIR)) { if (not (has-value $paths $E:GHOSTTY_BIN_DIR)) { diff --git a/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish b/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish index 7568dd566..3f1f6099e 100644 --- a/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish +++ b/src/shell-integration/fish/vendor_conf.d/ghostty-shell-integration.fish @@ -72,11 +72,14 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration" set -g __ghostty_prompt_start_mark "\e]133;A;click_events=1\a" end - if contains cursor $features + if string match -q 'cursor*' -- $features + set -l cursor 5 # blinking bar + contains cursor:steady $features && set cursor 6 # steady bar + # Change the cursor to a beam on prompt. - function __ghostty_set_cursor_beam --on-event fish_prompt -d "Set cursor shape" + function __ghostty_set_cursor_beam --on-event fish_prompt -V cursor -d "Set cursor shape" if not functions -q fish_vi_cursor_handle - echo -en "\e[5 q" + echo -en "\e[$cursor q" end end function __ghostty_reset_cursor --on-event fish_preexec -d "Reset cursor shape" @@ -233,7 +236,7 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration" set --global fish_handle_reflow 1 # Initial calls for first prompt - if contains cursor $features + if string match -q 'cursor*' -- $features __ghostty_set_cursor_beam end __ghostty_mark_prompt_start diff --git a/src/shell-integration/zsh/ghostty-integration b/src/shell-integration/zsh/ghostty-integration index c17de669a..1e0eb95aa 100644 --- a/src/shell-integration/zsh/ghostty-integration +++ b/src/shell-integration/zsh/ghostty-integration @@ -227,14 +227,14 @@ _ghostty_deferred_init() { # executed from zle. For example, users of fzf-based widgets may find # themselves with a blinking block cursor within fzf. _ghostty_zle_line_init _ghostty_zle_line_finish _ghostty_zle_keymap_select() { - case ${KEYMAP-} in - # Blinking block cursor. - vicmd|visual) builtin print -nu "$_ghostty_fd" '\e[1 q';; - # Blinking bar cursor. - *) builtin print -nu "$_ghostty_fd" '\e[5 q';; - esac + builtin local steady=0 + [[ "$GHOSTTY_SHELL_FEATURES" == *"cursor:steady"* ]] && steady=1 + case ${KEYMAP-} in + vicmd|visual) builtin print -nu "$_ghostty_fd" "\e[$(( 1 + steady )) q" ;; # block + *) builtin print -nu "$_ghostty_fd" "\e[$(( 5 + steady )) q" ;; # bar + esac } - # Restore the blinking default shape before executing an external command + # Restore the default shape before executing an external command functions[_ghostty_preexec]+=" builtin print -rnu $_ghostty_fd \$'\\e[0 q'" fi diff --git a/src/termio/Exec.zig b/src/termio/Exec.zig index 0e7cdc172..4443f324b 100644 --- a/src/termio/Exec.zig +++ b/src/termio/Exec.zig @@ -562,6 +562,7 @@ pub const Config = struct { env_override: configpkg.RepeatableStringMap = .{}, shell_integration: configpkg.Config.ShellIntegration = .detect, shell_integration_features: configpkg.Config.ShellIntegrationFeatures = .{}, + cursor_blink: ?bool = null, working_directory: ?[]const u8 = null, resources_dir: ?[]const u8, term: []const u8, @@ -755,6 +756,7 @@ const Subprocess = struct { try shell_integration.setupFeatures( &env, cfg.shell_integration_features, + cfg.cursor_blink orelse true, ); const force: ?shell_integration.Shell = switch (cfg.shell_integration) { diff --git a/src/termio/shell_integration.zig b/src/termio/shell_integration.zig index ab6dcd6ff..e5b9eab10 100644 --- a/src/termio/shell_integration.zig +++ b/src/termio/shell_integration.zig @@ -188,11 +188,13 @@ test detectShell { pub fn setupFeatures( env: *EnvMap, features: config.ShellIntegrationFeatures, + cursor_blink: bool, ) !void { const fields = @typeInfo(@TypeOf(features)).@"struct".fields; const capacity: usize = capacity: { comptime var n: usize = fields.len - 1; // commas inline for (fields) |field| n += field.name.len; + n += ":steady".len; // cursor value break :capacity n; }; @@ -221,6 +223,10 @@ pub fn setupFeatures( if (@field(features, name)) { if (writer.end > 0) try writer.writeByte(','); try writer.writeAll(name); + + if (std.mem.eql(u8, name, "cursor")) { + try writer.writeAll(if (cursor_blink) ":blink" else ":steady"); + } } } @@ -241,8 +247,8 @@ test "setup features" { var env = EnvMap.init(alloc); defer env.deinit(); - try setupFeatures(&env, .{ .cursor = true, .sudo = true, .title = true, .@"ssh-env" = true, .@"ssh-terminfo" = true, .path = true }); - try testing.expectEqualStrings("cursor,path,ssh-env,ssh-terminfo,sudo,title", env.get("GHOSTTY_SHELL_FEATURES").?); + try setupFeatures(&env, .{ .cursor = true, .sudo = true, .title = true, .@"ssh-env" = true, .@"ssh-terminfo" = true, .path = true }, true); + try testing.expectEqualStrings("cursor:blink,path,ssh-env,ssh-terminfo,sudo,title", env.get("GHOSTTY_SHELL_FEATURES").?); } // Test: all features disabled @@ -250,7 +256,7 @@ test "setup features" { var env = EnvMap.init(alloc); defer env.deinit(); - try setupFeatures(&env, std.mem.zeroes(config.ShellIntegrationFeatures)); + try setupFeatures(&env, std.mem.zeroes(config.ShellIntegrationFeatures), true); try testing.expect(env.get("GHOSTTY_SHELL_FEATURES") == null); } @@ -259,9 +265,25 @@ test "setup features" { var env = EnvMap.init(alloc); defer env.deinit(); - try setupFeatures(&env, .{ .cursor = false, .sudo = true, .title = false, .@"ssh-env" = true, .@"ssh-terminfo" = false, .path = false }); + try setupFeatures(&env, .{ .cursor = false, .sudo = true, .title = false, .@"ssh-env" = true, .@"ssh-terminfo" = false, .path = false }, true); try testing.expectEqualStrings("ssh-env,sudo", env.get("GHOSTTY_SHELL_FEATURES").?); } + + // Test: blinking cursor + { + var env = EnvMap.init(alloc); + defer env.deinit(); + try setupFeatures(&env, .{ .cursor = true, .sudo = false, .title = false, .@"ssh-env" = false, .@"ssh-terminfo" = false, .path = false }, true); + try testing.expectEqualStrings("cursor:blink", env.get("GHOSTTY_SHELL_FEATURES").?); + } + + // Test: steady cursor + { + var env = EnvMap.init(alloc); + defer env.deinit(); + try setupFeatures(&env, .{ .cursor = true, .sudo = false, .title = false, .@"ssh-env" = false, .@"ssh-terminfo" = false, .path = false }, false); + try testing.expectEqualStrings("cursor:steady", env.get("GHOSTTY_SHELL_FEATURES").?); + } } /// Setup the bash automatic shell integration. This works by