Support OSC133 click_events Kitty extension (supported by Fish) (#10536)

This adds support for the `OSC 133 A click_events=1` extension
introduced by Kitty and supported by Fish.[^1]

**What this means:** If the shell advertises `click_events=1` support,
Ghostty will _unconditionally_ (no modifier required) send mouse events
to the shell for clicks on a prompt line, delegating to the supporting
shell to move the cursor as needed. For Fish 4.1+ this means that
clicking on the prompt line moves the cursor (see demo video below).

This PR also contains:

* A minor fix in `cl` parsing but we don't yet implement the logic there
* Updated inspector to show the semantic prompt click mode

## Demo


https://github.com/user-attachments/assets/03ef8975-7ad9-441f-aaa2-9d0eb5c5e36d

## Implementation Details

`click_events` is wildly underspecified, so here are the details the
best I understand them. This itself is not a specification (I omit
details) but adds some more context to it.

The `click_events=1` option can be specified with `OSC 133 A` (Ghostty
also allows it on OSC 133 N). When that is specified, it flags for all
future prompts that the screen supports click events for semantic
prompts. If both `click_events` and `cl` are specified, `click_events`
takes priority if true. If `click_events=0` (disable), then any set `cl`
will take priority.

When a mouse click comes in, we check for the following conditions:

1. The screen supports click events
2. The screen cursor is currently at a prompt
3. The mouse click was at or below the starting prompt line of the
current prompt

If those are met, we encode an SGR mouse event with: left button, press,
coordinates of click. It is up to the shell after that to handle it. Out
of prompt bounds SGR events are possible (specifically below). The shell
should robustly handle this.

[^1]: I don't know any other terminal or shell that supports it at the
moment.
This commit is contained in:
Mitchell Hashimoto
2026-02-02 10:57:30 -08:00
committed by GitHub
6 changed files with 366 additions and 33 deletions

View File

@@ -51,6 +51,27 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration"
set --local features (string split , $GHOSTTY_SHELL_FEATURES)
# Parse the fish version for feature detection.
# Default to 0.0 if version is unavailable or malformed.
set -l fish_major 0
set -l fish_minor 0
if set -q version[1]
set -l fish_ver (string match -r '(\d+)\.(\d+)' -- $version[1])
if set -q fish_ver[2]; and test -n "$fish_ver[2]"
set fish_major "$fish_ver[2]"
end
if set -q fish_ver[3]; and test -n "$fish_ver[3]"
set fish_minor "$fish_ver[3]"
end
end
# Our OSC133A (prompt start) sequence. If we're using Fish >= 4.1
# then it supports click_events so we enable that.
set -g __ghostty_prompt_start_mark "\e]133;A\a"
if test "$fish_major" -gt 4; or test "$fish_major" -eq 4 -a "$fish_minor" -ge 1
set -g __ghostty_prompt_start_mark "\e]133;A;click_events=1\a"
end
if contains cursor $features
# Change the cursor to a beam on prompt.
function __ghostty_set_cursor_beam --on-event fish_prompt -d "Set cursor shape"
@@ -72,14 +93,14 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration"
# When using sudo shell integration feature, ensure $TERMINFO is set
# and `sudo` is not already a function or alias
if contains sudo $features; and test -n "$TERMINFO"; and test "file" = (type -t sudo 2> /dev/null; or echo "x")
if contains sudo $features; and test -n "$TERMINFO"; and test file = (type -t sudo 2> /dev/null; or echo "x")
# Wrap `sudo` command to ensure Ghostty terminfo is preserved
function sudo -d "Wrap sudo to preserve terminfo"
set --function sudo_has_sudoedit_flags "no"
set --function sudo_has_sudoedit_flags no
for arg in $argv
# Check if argument is '-e' or '--edit' (sudoedit flags)
if string match -q -- "-e" "$arg"; or string match -q -- "--edit" "$arg"
set --function sudo_has_sudoedit_flags "yes"
if string match -q -- -e "$arg"; or string match -q -- --edit "$arg"
set --function sudo_has_sudoedit_flags yes
break
end
# Check if argument is neither an option nor a key-value pair
@@ -87,7 +108,7 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration"
break
end
end
if test "$sudo_has_sudoedit_flags" = "yes"
if test "$sudo_has_sudoedit_flags" = yes
command sudo $argv
else
command sudo --preserve-env=TERMINFO $argv
@@ -100,7 +121,7 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration"
if contains ssh-env $features; or contains ssh-terminfo $features
function ssh --wraps=ssh --description "SSH wrapper with Ghostty integration"
set -l features (string split ',' -- "$GHOSTTY_SHELL_FEATURES")
set -l ssh_term "xterm-256color"
set -l ssh_term xterm-256color
set -l ssh_opts
# Configure environment variables for remote session
@@ -134,7 +155,7 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration"
# Check if terminfo is already cached
if test -x "$GHOSTTY_BIN_DIR/ghostty"; and "$GHOSTTY_BIN_DIR/ghostty" +ssh-cache --host="$ssh_target" >/dev/null 2>&1
set ssh_term "xterm-ghostty"
set ssh_term xterm-ghostty
else if command -q infocmp
set -l ssh_terminfo
set -l ssh_cpath_dir
@@ -154,7 +175,7 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration"
mkdir -p ~/.terminfo 2>/dev/null && tic -x - 2>/dev/null && exit 0
exit 1
' 2>/dev/null
set ssh_term "xterm-ghostty"
set ssh_term xterm-ghostty
set -a ssh_opts -o "ControlPath=$ssh_cpath"
# Cache successful installation
@@ -186,7 +207,7 @@ function __ghostty_setup --on-event fish_prompt -d "Setup ghostty integration"
end
set --global __ghostty_prompt_state prompt-start
echo -en "\e]133;A\a"
echo -en $__ghostty_prompt_start_mark
end
function __ghostty_mark_output_start --on-event fish_preexec