mirror of
https://github.com/ghostty-org/ghostty.git
synced 2026-04-14 03:25:50 +00:00
shell-integration: respect cursor-style-blink
The `cursor` shell feature always used a blinking bar (beam), often to the surprise of users who set `cursor-style-blink = false`. This change extends our GHOSTTY_SHELL_FEATURES format to include either `cursor:blink` (default) or `cursor:steady` based on cursor-style-blink when the `cursor` feature is enabled, and all shell integrations have been updated to use that additional information to choose the DECSCUSR cursor value (5=blinking bar, 6=steady bar). I also considered passing a DECSCUSR value in GHOSTTY_SHELL_FEATURES (e.g. `cursor:5`). This mostly worked well, but zsh also needs the blink state on its own for its block cursor. We also don't support any other shell feature cursor configurability (e.g. the shape), so this was an over generalization. This does change the behavior for users who like the blinking bar in the shell but have `cursor-blink-style = false` for other reasons. We could provide additional `cursor` shell feature configurability, but I think that's best left to a separate change.
This commit is contained in:
@@ -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,
|
||||
|
||||
@@ -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.
|
||||
///
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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)) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user