Shell integration¶
kitty has the ability to integrate closely within common shells, such as zsh, fish and bash to enable features such as jumping to previous prompts in the scrollback, viewing the output of the last command in less, using the mouse to move the cursor while editing prompts, etc.
New in version 0.24.0.
Features¶
Open the output of the last command in a pager such as less (
ctrl+shift+g
)Jump to the previous/next prompt in the scrollback (
ctrl+shift+z
/ctrl+shift+x
)Click with the mouse anywhere in the current command to move the cursor there
Hold ctrl+shift and right-click on any command output in the scrollback to view it in a pager
The current working directory or the command being executed are automatically displayed in the kitty window titlebar/tab title
The text cursor is changed to a bar when editing commands at the shell prompt
Glitch free window resizing even with complex prompts. Achieved by erasing the prompt on resize and allowing the shell to redraw it cleanly.
Sophisticated completion for the kitty command in the shell.
When confirming a quit command if a window is sitting at a shell prompt, it is optionally, not counted (see
confirm_os_window_close
)
Configuration¶
Shell integration is controlled by the shell_integration
option. By
default, all shell integration is enabled. Individual features can be turned
off or it can be disabled entirely as well. The shell_integration
option
takes a space separated list of keywords:
- disabled
Turn off all shell integration
- no-rc
Do not modify the shell's launch environment to enable integration. Useful if you prefer to manually enable integration.
- no-cursor
Turn off changing of the text cursor to a bar when editing text
- no-title
Turn off setting the kitty window/tab title based on shell state. Note that for the
fish
shell kitty relies on fish's native title setting functionality instead.- no-prompt-mark
Turn off marking of prompts. This disables jumping to prompt, browsing output of last command and click to move cursor functionality.
- no-complete
Turn off completion for the kitty command. Note that for the
fish
shell this does not take effect, since fish already comes with a kitty completion script.
More ways to browse command output¶
You can add further key and mouse bindings to browse the output of commands
easily. For example to select the output of a command by right clicking the mouse
on the output, define the following in kitty.conf
:
mouse_map right press ungrabbed mouse_select_command_output
Now, when you right click on the output, the entire output is selected, ready to be copied.
The feature to jump to previous prompts (
ctrl+shift+z
and ctrl+shift+x
) and mouse
actions (mouse_select_command_output and mouse_show_command_output) can
be integrated with browsing command output as well. For example, define the
following mapping in kitty.conf
:
map f1 show_last_visited_command_output
Now, pressing F1 will cause the output of the last jumped to command or the last mouse clicked command output to be opened in a pager for easy browsing.
In addition, You can define shortcut to get the first command output on screen.
For example, define the following in kitty.conf
:
map f1 show_first_command_output_on_screen
Now, pressing F1 will cause the output of the first command output on screen to be opened in a pager.
You can also add shortcut to scroll to the last jumped position. For example,
define the following in kitty.conf
:
map f1 scroll_to_prompt 0
How it works¶
At startup, kitty detects if the shell you have configured (either system wide or in kitty.conf) is a supported shell. If so, kitty injects some shell specific code into the shell, to enable shell integration. How it does so varies for different shells.
For zsh, kitty sets the ZDOTDIR
environment variable to make zsh load
kitty's .zshenv
which restores the original value of ZDOTDIR
and sources the original .zshenv
. It then loads the shell integration code.
The remainder of zsh's startup process proceeds as normal.
For fish, to make it automatically load the integration code provided by
kitty, the integration script directory path is prepended to the
XDG_DATA_DIRS
environment variable. This is only applied to the fish
process and will be cleaned up by the integration script after startup. No files
are added or modified.
For bash, kitty adds a couple of lines to the bottom of ~/.bashrc
(in an atomic manner) to load the shell integration code.
Then, when launching the shell, kitty sets the environment variable
KITTY_SHELL_INTEGRATION
to the value of the shell_integration
option. The shell integration code reads the environment variable, turns on the
specified integration functionality and then unsets the variable so as to not
pollute the system. This has the nice effect that the changes to the shell's rc
files become no-ops when running the shell in anything other than kitty itself.
The actual shell integration code uses hooks provided by each shell to send special escape codes to kitty, to perform the various tasks. You can see the code used for each shell below:
Click to toggle shell integration code
#!/bin/zsh
#
# Enables integration between zsh and kitty based on KITTY_SHELL_INTEGRATION.
# The latter is set by kitty based on kitty.conf.
#
# This is an autoloadable function. It's invoked automatically in shells
# directly spawned by kitty but not in any other shells. For example, running
# `exec zsh`, `sudo -E zsh`, `tmux`, or plain `zsh` will create a shell where
# kitty-integration won't automatically run. Zsh users who want integration with
# kitty in all shells should add the following lines to their .zshrc:
#
# if [[ -n $KITTY_INSTALLATION_DIR ]]; then
# export KITTY_SHELL_INTEGRATION="enabled"
# autoload -Uz -- "$KITTY_INSTALLATION_DIR"/shell-integration/zsh/kitty-integration
# kitty-integration
# unfunction kitty-integration
# fi
#
# Implementation note: We can assume that alias expansion is disabled in this
# file, so no need to quote defensively. We still have to defensively prefix all
# builtins with `builtin` to avoid accidentally invoking user-defined functions.
# We avoid `function` reserved word as an additional defensive measure.
builtin emulate -L zsh -o no_warn_create_global -o no_aliases
[[ -o interactive ]] || builtin return 0 # non-interactive shell
[[ -n $KITTY_SHELL_INTEGRATION ]] || builtin return 0 # integration disabled
(( ! $+_ksi_state )) || builtin return 0 # already initialized
# 0: no OSC 133 [AC] marks have been written yet.
# 1: the last written OSC 133 C has not been closed with D yet.
# 2: none of the above.
builtin typeset -gi _ksi_state
# Attempt to create a writable file descriptor to the TTY so that we can print
# to the TTY later even when STDOUT is redirected. This code is fairly subtle.
#
# - It's tempting to do `[[ -t 1 ]] && exec {_ksi_state}>&1` but we cannot do this
# because it'll create a file descriptor >= 10 without O_CLOEXEC. This file
# descriptor will leak to child processes.
# - If we do `exec {3}>&1`, the file descriptor won't leak to the child processes
# but it'll still leak if the current process is replaced with another. In
# addition, it'll break user code that relies on fd 3 being available.
# - Zsh doesn't expose dup3, which would have allowed us to copy STDOUT with
# O_CLOEXEC. The only way to create a file descriptor with O_CLOEXEC is via
# sysopen.
# - `zmodload zsh/system` and `sysopen -o cloexec -wu _ksi_fd -- /dev/tty` can
# fail with an error message to STDERR (the latter can happen even if /dev/tty
# is writable), hence the redirection of STDERR. We do it for the whole block
# for performance reasons (redirections are slow).
# - We must open the file descriptor right here rather than in _ksi_deferred_init
# because there are broken zsh plugins out there that run `exec {fd}< <(cmd)`
# and then close the file descriptor more than once while suppressing errors.
# This could end up closing our file descriptor if we opened it in
# _ksi_deferred_init.
typeset -gi _ksi_fd
{
zmodload zsh/system && (( $+builtins[sysopen] )) && {
{ [[ -w $TTY ]] && sysopen -o cloexec -wu _ksi_fd -- $TTY } ||
{ [[ -w /dev/tty ]] && sysopen -o cloexec -wu _ksi_fd -- /dev/tty }
}
} 2>/dev/null || (( _ksi_fd = 1 ))
# Asks kitty to print $@ to its STDOUT. This is for debugging.
_ksi_debug_print() {
builtin local data
data=$(command base64 <<<"${(j: :)@}") || builtin return
# Removing all spaces rather than just \n allows this code to
# work on broken systems where base64 outputs \r\n.
builtin print -nu "$_ksi_fd" '\eP@kitty-print|'"${data//[[:space:]]}"'\e\\'
}
# We defer initialization until precmd for several reasons:
#
# - Oh My Zsh and many other configs remove zle-line-init and
# zle-line-finish hooks when they initialize.
# - By deferring initialization we allow user rc files to opt out from some
# parts of integration. For example, if a zshrc theme prints OSC 133
# marks, it can append " no-prompt-mark" to KITTY_SHELL_INTEGRATION during
# initialization to avoid redundant marks from our code.
builtin typeset -ag precmd_functions
precmd_functions+=(_ksi_deferred_init)
_ksi_deferred_init() {
builtin emulate -L zsh -o no_warn_create_global -o no_aliases
# Recognized options: no-cursor, no-title, no-prompt-mark, no-complete.
builtin local -a opt
opt=(${(s: :)KITTY_SHELL_INTEGRATION})
unset KITTY_SHELL_INTEGRATION
# The directory where kitty-integration is located: /.../shell-integration/zsh.
builtin local self_dir=${functions_source[_ksi_deferred_init]:A:h}
# The directory with _kitty. We store it in a directory of its own rather than
# in $self_dir because we are adding it to fpath and we don't want any other
# files to be accidentally autoloadable.
builtin local comp_dir=$self_dir/completions
# Enable completions for `kitty` command.
if (( ! opt[(Ie)no-complete] )) && [[ -r $comp_dir/_kitty ]]; then
if (( $+functions[compdef] )); then
# If compdef is defined, then either compinit has already run or it's
# a shim that records all calls for the purpose of replaying them after
# compinit. Either way we clobber the existing completion for kitty and
# install our own.
builtin unset "functions[_kitty]"
builtin autoload -Uz -- $comp_dir/_kitty
compdef _kitty kitty
fi
# If compdef is not set, compinit has not run yet. In this case we must
# add our completions directory to fpath so that _kitty gets picked up by
# compinit.
#
# We extend fpath even if compinit has run because it might run again.
# Without our completions directory in fpath compinit would our _comp
# mapping.
builtin typeset -ga fpath
fpath=($comp_dir ${fpath:#$comp_dir})
fi
# Enable semantic markup with OSC 133.
if (( ! opt[(Ie)no-prompt-mark] )); then
_ksi_precmd() {
builtin local -i cmd_status=$?
builtin emulate -L zsh -o no_warn_create_global -o no_aliases
# Don't write OSC 133 D when our precmd handler is invoked from zle.
# Some plugins do that to update prompt on cd.
if ! builtin zle; then
# This code works incorrectly in the presence of a precmd or chpwd
# hook that prints. For example, sindresorhus/pure prints an empty
# line on precmd and marlonrichert/zsh-snap prints $PWD on chpwd.
# We'll end up writing our OSC 133 D mark too late.
#
# Another failure mode is when the output of a command doesn't end
# with LF and prompst_sp is set (it is by default). In this case
# we'll incorrectly state that '%' from prompt_sp is a part of the
# command's output.
if (( _ksi_state == 1 )); then
# The last written OSC 133 C has not been closed with D yet.
# Close it and supply status.
builtin print -nu $_ksi_fd '\e]133;D;'$cmd_status'\a'
(( _ksi_state = 2 ))
elif (( _ksi_state == 2 )); then
# There might be an unclosed OSC 133 C. Close that.
builtin print -nu $_ksi_fd '\e]133;D\a'
fi
fi
builtin local mark1=$'%{\e]133;A\a%}'
if [[ -o prompt_percent ]]; then
builtin typeset -g precmd_functions
if [[ ${precmd_functions[-1]} == _ksi_precmd ]]; then
# This is the best case for us: we can add our marks to PS1 and
# PS2. This way our marks will be printed whenever zsh
# redisplays prompt: on reset-prompt, on SIGWINCH, and on
# SIGCHLD if notify is set. Themes that update prompt
# asynchronously from a `zle -F` handler might still remove our
# marks. Oh well.
builtin local mark2=$'%{\e]133;A;k=s\a%}'
# Add marks conditionally to avoid a situation where we have
# several marks in place. These conditions can have false
# positives and false negatives though.
#
# - False positive (with prompt_percent): PS1="%(?.$mark1.)"
# - False negative (with prompt_subst): PS1='$mark1'
[[ $PS1 == *$mark1* ]] || PS1=${mark1}${PS1}
# PS2 mark is needed when clearing the prompt on resize
[[ $PS2 == *$mark2* ]] || PS2=${mark2}${PS2}
(( _ksi_state = 2 ))
else
# If our precmd hook is not the last, we cannot rely on prompt
# changes to stick, so we don't even try. At least we can move
# our hook to the end to have better luck next time. If there is
# another piece of code that wants to take this privileged
# position, this won't work well. We'll break them as much as
# they are breaking us.
precmd_functions=(${precmd_functions:#_ksi_precmd} _ksi_precmd)
# Plugins that invoke precmd hooks from zle do that before zle
# is trashed. This means that the cursor is in the middle of
# BUFFER and we cannot print our mark there. Prompt might
# already have a mark, so the following reset-prompt will write
# it. If it doesn't, there is nothing we can do.
if ! builtin zle; then
builtin print -rnu $_ksi_fd -- $mark1[3,-3]
(( _ksi_state = 2 ))
fi
fi
elif ! builtin zle; then
# Without prompt_percent we cannot patch prompt. Just print the
# mark, except when we are invoked from zle. In the latter case we
# cannot do anything.
builtin print -rnu $_ksi_fd -- $mark1[3,-3]
(( _ksi_state = 2 ))
fi
}
_ksi_preexec() {
builtin emulate -L zsh -o no_warn_create_global -o no_aliases
# This can potentially break user prompt. Oh well. The robustness of
# this code can be improved in the case prompt_subst is set because
# it'll allow us distinguish (not perfectly but close enough) between
# our own prompt, user prompt, and our own prompt with user additions on
# top. We cannot force prompt_subst on the user though, so we would
# still need this code for the no_prompt_subst case.
PS1=${PS1//$'%{\e]133;A\a%}'}
PS2=${PS2//$'%{\e]133;A;k=s\a%}'}
# This will work incorrectly in the presence of a preexec hook that
# prints. For example, if MichaelAquilina/zsh-you-should-use installs
# its preexec hook before us, we'll incorrectly mark its output as
# belonging to the command (as if the user typed it into zle) rather
# than command output.
builtin print -nu $_ksi_fd '\e]133;C\a'
(( _ksi_state = 1 ))
}
# the following two lines are commented out as currently kitty doesn't use B prompt marking
# and hooking zle widgets in ZSH is a total minefield, see https://github.com/kovidgoyal/kitty/issues/4428
# so we can at least tell users to use no-cursor and with that avoid hooking ZLE widgets at all
# functions[_ksi_zle_line_init]+='
# builtin print -nu "$_ksi_fd" "\\e]133;B\\a"'
fi
# Enable terminal title changes.
if (( ! opt[(Ie)no-title] )); then
# We don't use `print -P` because it depends on prompt options, which
# we don't control and cannot change.
#
# We use (V) in preexec to convert control characters to something visible
# (LF becomes \n, etc.). This isn't necessary in precmd because (%) does it
# for us.
functions[_ksi_precmd]+="
builtin print -rnu $_ksi_fd \$'\\e]2;'\"\${(%):-%(4~|…/%3~|%~)}\"\$'\\a'"
functions[_ksi_preexec]+="
builtin print -rnu $_ksi_fd \$'\\e]2;'\"\${(V)1}\"\$'\\a'"
fi
# Enable cursor shape changes depending on the current keymap.
if (( ! opt[(Ie)no-cursor] )); then
# This implementation leaks blinking block cursor into external commands
# executed from zle. For example, users of fzf-based widgets may find
# themselves with a blinking block cursor within fzf.
_ksi_zle_line_init _ksi_zle_line_finish _ksi_zle_keymap_select() {
case ${KEYMAP-} in
# Blinking block cursor.
vicmd|visual) builtin print -nu "$_ksi_fd" '\e[1 q';;
# Blinking bar cursor.
*) builtin print -nu "$_ksi_fd" '\e[5 q';;
esac
}
# Restore the blinking default shape before executing an external command
functions[_ksi_preexec]+="
builtin print -rnu $_ksi_fd \$'\\e[0 q'"
fi
# Some zsh users manually run `source ~/.zshrc` in order to apply rc file
# changes to the current shell. This is a terrible practice that breaks many
# things, including our shell integration. For example, Oh My Zsh and Prezto
# (both very popular among zsh users) will remove zle-line-init and
# zle-line-finish hooks if .zshrc is manually sourced. Prezto will also remove
# zle-keymap-select.
#
# Another common (and much more robust) way to apply rc file changes to the
# current shell is `exec zsh`. This will remove our integration from the shell
# unless it's explicitly invoked from .zshrc. This is not an issue with
# `exec zsh` but rather with our implementation of automatic shell integration.
# In the ideal world we would use add-zle-hook-widget to hook zle-line-init
# and similar widget. This breaks user configs though, so we have do this
# horrible thing instead.
builtin local hook func widget orig_widget flag
for hook in line-init line-finish keymap-select; do
func=_ksi_zle_${hook/-/_}
(( $+functions[$func] )) || builtin continue
widget=zle-$hook
if [[ $widgets[$widget] == user:azhw:* &&
$+functions[add-zle-hook-widget] -eq 1 ]]; then
# If the widget is already hooked by add-zle-hook-widget at the top
# level, add our hook at the end. We MUST do it this way. We cannot
# just wrap the widget ourselves in this case because it would
# trigger bugs in add-zle-hook-widget.
add-zle-hook-widget $hook $func
else
if (( $+widgets[$widget] )); then
# There is a widget but it's not from add-zle-hook-widget. We
# can rename the original widget, install our own and invoke
# the original when we are called.
#
# Note: The leading dot is to work around bugs in
# zsh-syntax-highlighting.
orig_widget=._ksi_orig_$widget
builtin zle -A $widget $orig_widget
if [[ $widgets[$widget] == user:* ]]; then
# No -w here to preserve $WIDGET within the original widget.
flag=
else
flag=w
fi
functions[$func]+="
builtin zle $orig_widget -N$flag -- \"\$@\""
fi
builtin zle -N $widget $func
fi
done
if (( $+functions[_ksi_preexec] )); then
builtin typeset -ag preexec_functions
preexec_functions+=(_ksi_preexec)
fi
builtin typeset -ag precmd_functions
if (( $+functions[_ksi_precmd] )); then
precmd_functions=(${precmd_functions:/_ksi_deferred_init/_ksi_precmd})
_ksi_precmd
else
precmd_functions=(${precmd_functions:#_ksi_deferred_init})
fi
# Unfunction _ksi_deferred_init to save memory. Don't unfunction
# kitty-integration though because decent public functions aren't supposed to
# to unfunction themselves when invoked. Unfunctioning is done by calling code.
builtin unfunction _ksi_deferred_init
}
#!/bin/fish
status is-interactive || exit 0
not functions -q __ksi_schedule || exit 0
function __ksi_schedule --on-event fish_prompt -d "Setup kitty integration after other scripts have run, we hope"
functions --erase __ksi_schedule
test -n "$KITTY_SHELL_INTEGRATION" || return 0
# To use fish's autoloading feature, kitty prepends the vendored integration script directory to XDG_DATA_DIRS.
# The original paths needs to be restored here to not affect other programs.
# In particular, if the original XDG_DATA_DIRS does not exist, it needs to be removed.
if set -q XDG_DATA_DIRS KITTY_FISH_XDG_DATA_DIR
set --global --export --path XDG_DATA_DIRS "$XDG_DATA_DIRS"
if set -l index (contains -i "$KITTY_FISH_XDG_DATA_DIR" $XDG_DATA_DIRS)
set --erase --global XDG_DATA_DIRS[$index]
test -n "$XDG_DATA_DIRS" || set --erase --global XDG_DATA_DIRS
end
if set -q XDG_DATA_DIRS
set --global --export --unpath XDG_DATA_DIRS "$XDG_DATA_DIRS"
end
end
set --local _ksi (string split " " -- "$KITTY_SHELL_INTEGRATION")
set --erase KITTY_SHELL_INTEGRATION KITTY_FISH_XDG_DATA_DIR
# Enable cursor shape changes for default mode and vi mode
if not contains "no-cursor" $_ksi
and not functions -q __ksi_set_cursor
function __ksi_block_cursor --on-event fish_preexec -d "Set cursor shape to blinking default shape before executing command"
printf "\e[0 q"
end
function __ksi_set_cursor --on-variable fish_key_bindings -d "Set the cursor shape for different modes when switching key bindings"
if test "$fish_key_bindings" = fish_default_key_bindings
not functions -q __ksi_bar_cursor || return
function __ksi_bar_cursor --on-event fish_prompt -d "Set cursor shape to blinking bar on prompt"
printf "\e[5 q"
end
else
functions --erase __ksi_bar_cursor
contains "$fish_key_bindings" fish_vi_key_bindings fish_hybrid_key_bindings
and __ksi_set_vi_cursor
end
end
function __ksi_set_vi_cursor -d "Set the vi mode cursor shapes"
# Set the vi mode cursor shapes only when none of them are configured
set --local vi_modes fish_cursor_{default,insert,replace_one,visual}
set -q $vi_modes
test "$status" -eq 4 || return
set --local vi_cursor_shapes block line underscore block
for i in 1 2 3 4
set --global $vi_modes[$i] $vi_cursor_shapes[$i] blink
end
# Change the cursor shape for current mode
test "$fish_bind_mode" = "insert" && printf "\e[5 q" || printf "\e[1 q"
end
__ksi_set_cursor
functions -q __ksi_bar_cursor
and __ksi_bar_cursor
end
# Enable prompt marking with OSC 133
if not contains "no-prompt-mark" $_ksi
and not set -q __ksi_prompt_state
set --global __ksi_prompt_state first-run
function __ksi_function_is_not_empty -d "Check if the specified function exists and is not empty"
functions --no-details $argv[1] | string match -qnvr '^ *(#|function |end$|$)'
end
function __ksi_mark -d "Tell kitty to mark the current cursor position using OSC 133"
printf "\e]133;%s\a" "$argv[1]"
end
function __ksi_prompt_start
# Preserve the command exit code from $status
set --local cmd_status $status
if contains "$__ksi_prompt_state" post-exec first-run
__ksi_mark D
end
set --global __ksi_prompt_state prompt-start
__ksi_mark A
return $cmd_status
end
function __ksi_prompt_end
set --local cmd_status $status
# fish trims one trailing newline from the output of fish_prompt, so
# we need to do the same. See https://github.com/kovidgoyal/kitty/issues/4032
set --local op (__ksi_original_fish_prompt) # op is a list because fish splits on newlines in command substitution
if set -q op[2]
printf '%s\n' $op[1..-2] # print all but last element of the list, each followed by a new line
end
printf '%s' $op[-1] # print the last component without a newline
set --global __ksi_prompt_state prompt-end
__ksi_mark B
return $cmd_status
end
functions -c fish_prompt __ksi_original_fish_prompt
if __ksi_function_is_not_empty fish_mode_prompt
# See https://github.com/starship/starship/issues/1283
# for why we have to test for a non-empty fish_mode_prompt
functions -c fish_mode_prompt __ksi_original_fish_mode_prompt
function fish_mode_prompt
__ksi_prompt_start
__ksi_original_fish_mode_prompt
end
function fish_prompt
__ksi_prompt_end
end
else
function fish_prompt
__ksi_prompt_start
__ksi_prompt_end
end
end
function __ksi_mark_output_start --on-event fish_preexec
set --global __ksi_prompt_state pre-exec
__ksi_mark C
end
function __ksi_mark_output_end --on-event fish_postexec
set --global __ksi_prompt_state post-exec
__ksi_mark "D;$status"
end
# With prompt marking kitty clears the current prompt on resize so we need
# fish to redraw it
set --global fish_handle_reflow 1
functions --erase __ksi_function_is_not_empty
end
end
#!/bin/bash
if [[ $- != *i* ]] ; then builtin return; fi # check in interactive mode
if [[ -z "$KITTY_SHELL_INTEGRATION" ]]; then builtin return; fi
# this is defined outside _ksi_main to make it global without using declare -g
# which is not available on older bash
builtin declare -A _ksi_prompt
_ksi_prompt=( [cursor]='y' [title]='y' [mark]='y' [complete]='y' [ps0]='' [ps1]='' [ps2]='' )
_ksi_main() {
for i in ${KITTY_SHELL_INTEGRATION[@]}; do
if [[ "$i" == "no-cursor" ]]; then _ksi_prompt[cursor]='n'; fi
if [[ "$i" == "no-title" ]]; then _ksi_prompt[title]='n'; fi
if [[ "$i" == "no-prompt-mark" ]]; then _ksi_prompt[mark]='n'; fi
if [[ "$i" == "no-complete" ]]; then _ksi_prompt[complete]='n'; fi
done
builtin unset KITTY_SHELL_INTEGRATION
_ksi_debug_print() {
# print a line to STDOUT of parent kitty process
builtin local b=$(command base64 <<< "${@}" | tr -d \\n)
builtin printf "\eP@kitty-print|%s\e\\" "$b"
# "
}
_ksi_set_mark() {
_ksi_prompt["${1}_mark"]="\[\e]133;k;${1}_kitty\a\]"
}
_ksi_set_mark start
_ksi_set_mark end
_ksi_set_mark start_secondary
_ksi_set_mark end_secondary
builtin unset -f _ksi_set_mark
_ksi_prompt[secondary_prompt]="\n${_ksi_prompt[start_secondary_mark]}\[\e]133;A;k=s\a\]${_ksi_prompt[end_secondary_mark]}"
_ksi_prompt_command() {
# we first remove any previously added kitty code from the prompt variables and then add
# it back, to ensure we have only a single instance
if [[ -n "${_ksi_prompt[ps0]}" ]]; then
PS0=${PS0//\\\[\\e\]133;k;start_kitty\\a\\\]*end_kitty\\a\\\]}
PS0="${_ksi_prompt[ps0]}$PS0"
fi
if [[ -n "${_ksi_prompt[ps1]}" ]]; then
PS1=${PS1//\\\[\\e\]133;k;start_kitty\\a\\\]*end_kitty\\a\\\]}
PS1="${_ksi_prompt[ps1]}$PS1"
if [[ "${_ksi_prompt[mark]}" == "y" ]]; then
# bash does not redraw the leading lines in a multiline prompt so
# mark them as secondary prompts
PS1=${PS1//\\\[\\e\]133;k;start_secondary_kitty\\a\\\]*end_secondary_kitty\\a\\\]}
PS1=${PS1//"\n"/${_ksi_prompt[secondary_prompt]}}
fi
fi
if [[ -n "${_ksi_prompt[ps2]}" ]]; then
PS2=${PS2//\\\[\\e\]133;k;start_kitty\\a\\\]*end_kitty\\a\\\]}
PS2="${_ksi_prompt[ps2]}$PS2"
fi
}
if [[ "${_ksi_prompt[cursor]}" == "y" ]]; then
_ksi_prompt[ps1]+="\[\e[5 q\]" # blinking bar cursor
_ksi_prompt[ps0]+="\[\e[0 q\]" # blinking default cursor
fi
if [[ "${_ksi_prompt[title]}" == "y" ]]; then
# see https://www.gnu.org/software/bash/manual/html_node/Controlling-the-Prompt.html#Controlling-the-Prompt
_ksi_prompt[ps1]+="\[\e]2;\w\a\]"
if [[ "$HISTCONTROL" == *"ignoreboth"* ]] || [[ "$HISTCONTROL" == *"ignorespace"* ]]; then
_ksi_debug_print "ignoreboth or ignorespace present in bash HISTCONTROL setting, showing running command in window title will not be robust"
fi
_ksi_get_current_command() {
local last_cmd=$(HISTTIMEFORMAT= builtin history 1)
last_cmd="${last_cmd#*[[:digit:]]*[[:space:]]}" # remove leading history number
last_cmd="${last_cmd#"${last_cmd%%[![:space:]]*}"}" # remove remaining leading whitespace
builtin printf "\e]2;%s\a" "${last_cmd}"
}
_ksi_prompt[ps0]+='$(_ksi_get_current_command)'
fi
if [[ "${_ksi_prompt[mark]}" == "y" ]]; then
_ksi_prompt[ps1]+="\[\e]133;A\a\]"
_ksi_prompt[ps2]+="\[\e]133;A;k=s\a\]"
_ksi_prompt[ps0]+="\[\e]133;C\a\]"
fi
if [[ "${_ksi_prompt[complete]}" == "y" ]]; then
_ksi_completions() {
builtin local src
builtin local limit
# Send all words up to the word the cursor is currently on
builtin let limit=1+$COMP_CWORD
src=$(builtin printf "%s\n" "${COMP_WORDS[@]:0:$limit}" | command kitty +complete bash)
if [[ $? == 0 ]]; then
builtin eval ${src}
fi
}
builtin complete -o nospace -F _ksi_completions kitty
fi
# wrap our prompt additions in markers we can use to remove them using
# bash's anemic pattern substitution
if [[ -n "${_ksi_prompt[ps0]}" ]]; then
_ksi_prompt[ps0]="${_ksi_prompt[start_mark]}${_ksi_prompt[ps0]}${_ksi_prompt[end_mark]}"
fi
if [[ -n "${_ksi_prompt[ps1]}" ]]; then
_ksi_prompt[ps1]="${_ksi_prompt[start_mark]}${_ksi_prompt[ps1]}${_ksi_prompt[end_mark]}"
fi
if [[ -n "${_ksi_prompt[ps2]}" ]]; then
_ksi_prompt[ps2]="${_ksi_prompt[start_mark]}${_ksi_prompt[ps2]}${_ksi_prompt[end_mark]}"
fi
builtin unset _ksi_prompt[start_mark]
builtin unset _ksi_prompt[end_mark]
builtin unset _ksi_prompt[start_secondary_mark]
builtin unset _ksi_prompt[end_secondary_mark]
# install our prompt command, using an array if it is unset or already an array,
# otherwise append a string
if [[ -z "${PROMPT_COMMAND}" ]]; then
PROMPT_COMMAND=([0]="_ksi_prompt_command")
elif [[ $(builtin declare -p PROMPT_COMMAND 2> /dev/null) =~ 'declare -a PROMPT_COMMAND' ]]; then
PROMPT_COMMAND+=("_ksi_prompt_command")
else
PROMPT_COMMAND+="; _ksi_prompt_command"
fi
}
_ksi_main
builtin unset -f _ksi_main
# freeze _ksi_prompt to prevent it from being changed
builtin declare -r _ksi_prompt
Manual shell integration¶
The automatic shell integration is designed to be minimally intrusive, as such it wont work for sub-shells, terminal multiplexers, containers, remote systems, etc. For such systems, you should setup manual shell integration by adding some code to your shells startup files to load the shell integration script.
First, in kitty.conf
set:
shell_integration disabled
Then in your shell's rc file, add the lines:
if test -n "$KITTY_INSTALLATION_DIR"; then
export KITTY_SHELL_INTEGRATION="enabled"
autoload -Uz -- "$KITTY_INSTALLATION_DIR"/shell-integration/zsh/kitty-integration
kitty-integration
unfunction kitty-integration
fi
if set -q KITTY_INSTALLATION_DIR
set --global KITTY_SHELL_INTEGRATION enabled
source "$KITTY_INSTALLATION_DIR/shell-integration/fish/vendor_conf.d/kitty-shell-integration.fish"
set --prepend fish_complete_path "$KITTY_INSTALLATION_DIR/shell-integration/fish/vendor_completions.d"
end
if test -n "$KITTY_INSTALLATION_DIR"; then
export KITTY_SHELL_INTEGRATION="enabled"
source "$KITTY_INSTALLATION_DIR/shell-integration/bash/kitty.bash"
fi
The value of KITTY_SHELL_INTEGRATION
is the same as that for
shell_integration
, except if you want to disable shell integration
completely, in which case simply do not set the
KITTY_SHELL_INTEGRATION
variable at all.
If you want this to work while SSHing into a remote system, then you will
need to add some code to the snippets above to check if KITTY_INSTALLATION_DIR
is empty and if so to set it to some hard coded location with the shell
integration scripts that need to be copied onto the remote system.
Integration with other shells¶
There exist third-party integrations to use these features for various other shells:
Notes for shell developers¶
The protocol used for marking the prompt is very simple. You should consider adding it to your shell as a builtin. Many modern terminals make use of it, for example: kitty, iTerm2, WezTerm, DomTerm
Just before starting to draw the PS1 prompt send the escape code:
<OSC>133;A<ST>
Just before starting to draw the PS2 prompt send the escape code:
<OSC>133;A;k=s<ST>
Just before running a command/program, send the escape code:
<OSC>133;C<ST>
Here <OSC>
is the bytes 0x1b 0x5d
and <ST>
is the bytes 0x1b
0x5c
. This is exactly what is needed for shell integration in kitty. For the
full protocol, that also marks the command region, see the iTerm2 docs.