Kovid Goyal 325603bf41
Shell integration zsh: Handle the case when a function that breaks prompt marking is installed after _ksi_precmd
We now install the real precmd hook only in the first run of the precmd
hook. This ensures that our precmd and preexec hooks are run last,
unless something else uses this trick, which is unlikely, and at that
point, the user is on their own.

Also ensure that the integration script is run only once even if the
user tries to source it twice with setting of KITTY_SHELL_INTEGRATION
each time.
2021-11-27 12:24:19 +05:30

169 lines
6.4 KiB
Bash

#!/bin/zsh
() {
if [[ ! -o interactive ]]; then return; fi
if [[ -z "$KITTY_SHELL_INTEGRATION" ]]; then return; fi
if [[ ! -z "$_ksi_prompt" ]]; then return; fi
typeset -g -A _ksi_prompt
_ksi_prompt=(state first-run is_last_precmd y cursor y title y mark y complete y)
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
unset KITTY_SHELL_INTEGRATION
function _ksi_debug_print() {
# print a line to STDOUT of parent kitty process
local b=$(printf "%s\n" "$1" | base64 | tr -d \\n)
printf "\eP@kitty-print|%s\e\\" "$b"
}
function _ksi_change_cursor_shape () {
# change cursor shape depending on mode
if [[ "$_ksi_prompt[cursor]" == "y" ]]; then
case $KEYMAP in
vicmd | visual)
# the command mode for vi
printf "\e[1 q" # blinking block cursor
;;
*)
printf "\e[5 q" # blinking bar cursor
;;
esac
fi
}
function _ksi_zle_keymap_select() {
_ksi_change_cursor_shape
}
function _ksi_zle_keymap_select_with_original() { zle kitty-zle-keymap-select-original; _ksi_zle_keymap_select }
zle -A zle-keymap-select kitty-zle-keymap-select-original 2>/dev/null
if [[ $? == 0 ]]; then
zle -N zle-keymap-select _ksi_zle_keymap_select_with_original
else
zle -N zle-keymap-select _ksi_zle_keymap_select
fi
function _ksi_osc() {
printf "\e]%s\a" "$1"
}
function _ksi_mark() {
# tell kitty to mark the current cursor position using OSC 133
if [[ "$_ksi_prompt[mark]" == "y" ]]; then _ksi_osc "133;$1"; fi
}
_ksi_prompt[start_mark]="%{$(_ksi_mark A)%}"
_ksi_prompt[secondary_mark]="%{$(_ksi_mark 'A;k=s')%}"
function _ksi_set_title() {
if [[ "$_ksi_prompt[title]" == "y" ]]; then _ksi_osc "2;$1"; fi
}
function _ksi_install_completion() {
if [[ "$_ksi_prompt[complete]" == "y" ]]; then
# compdef is only defined if compinit has been called
if whence compdef > /dev/null; then
compdef _ksi_complete kitty
fi
fi
}
function _ksi_precmd() {
local cmd_status=$?
# Set kitty window title to the cwd, appropriately shortened, see
# https://unix.stackexchange.com/questions/273529/shorten-path-in-zsh-prompt
_ksi_set_title $(print -P '%(4~|…/%3~|%~)')
# Prompt marking
if [[ "$_ksi_prompt[mark]" == "y" ]]; then
if [[ "$_ksi_prompt[state]" == "preexec" ]]; then
_ksi_mark "D;$cmd_status"
else
if [[ "$_ksi_prompt[state]" != "first-run" ]]; then _ksi_mark "D"; fi
fi
# we must use PS1 to set the prompt start mark as precmd functions are
# not called when the prompt is redrawn after a window resize or when a background
# job finishes. However, if we are not the last function in precmd_functions which
# can be the case on first run, PS1 might be broken by a following function, so
# output the mark directly in that case
if [[ "$_ksi_prompt[is_last_precmd]" != "y" ]]; then
_ksi_mark "A";
_ksi_prompt[is_last_precmd]="y";
else
if [[ "$PS1" != *"$_ksi_prompt[start_mark]"* ]]; then PS1="$_ksi_prompt[start_mark]$PS1" fi
fi
# PS2 is used for prompt continuation. On resize with a continued prompt only the last
# prompt is redrawn so we need to mark it
if [[ "$PS2" != *"$_ksi_prompt[secondary_mark]"* ]]; then PS2="$_ksi_prompt[secondary_mark]$PS2" fi
fi
_ksi_prompt[state]="precmd"
}
function _ksi_zle_line_init() {
if [[ "$_ksi_prompt[mark]" == "y" ]]; then _ksi_mark "B"; fi
_ksi_change_cursor_shape
_ksi_prompt[state]="line-init"
}
function _ksi_zle_line_init_with_orginal() { zle kitty-zle-line-init-original; _ksi_zle_line_init }
zle -A zle-line-init kitty-zle-line-init-original 2>/dev/null
if [[ $? == 0 ]]; then
zle -N zle-line-init _ksi_zle_line_init_with_orginal
else
zle -N zle-line-init _ksi_zle_line_init
fi
function _ksi_zle_line_finish() {
_ksi_change_cursor_shape
_ksi_prompt[state]="line-finish"
}
function _ksi_zle_line_finish_with_orginal() { zle kitty-zle-line-finish-original; _ksi_zle_line_finish }
zle -A zle-line-finish kitty-zle-line-finish-original 2>/dev/null
if [[ $? == 0 ]]; then
zle -N zle-line-finish _ksi_zle_line_finish_with_orginal
else
zle -N zle-line-finish _ksi_zle_line_finish
fi
function _ksi_preexec() {
if [[ "$_ksi_prompt[mark]" == "y" ]]; then
_ksi_mark "C";
# remove the prompt mark sequence while the command is executing as it could read/modify the value of PS1
PS1="${PS1//$_ksi_prompt[start_mark]/}"
PS2="${PS2//$_ksi_prompt[secondary_mark]/}"
fi
# Set kitty window title to the currently executing command
_ksi_set_title "$1"
_ksi_prompt[state]="preexec"
}
function _ksi_first_run() {
_ksi_install_completion
typeset -a -g precmd_functions
local idx=$precmd_functions[(ie)_ksi_first_run]
if [[ $idx -gt 0 ]]; then
if [[ $idx -lt ${#precmd_functions[@]} ]]; then
_ksi_prompt[is_last_precmd]="n"
fi
precmd_functions[$idx]=()
precmd_functions=($precmd_functions _ksi_precmd)
typeset -a -g preexec_functions
preexec_functions=($preexec_functions _ksi_preexec)
_ksi_precmd
fi
}
typeset -a -g precmd_functions
precmd_functions=($precmd_functions _ksi_first_run)
# Completion for kitty
_ksi_complete() {
local src
# Send all words up to the word the cursor is currently on
src=$(printf "%s\n" "${(@)words[1,$CURRENT]}" | kitty +complete zsh)
if [[ $? == 0 ]]; then
eval ${src}
fi
}
}