aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/shell-integration/zsh
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-04-04 19:22:04 +0300
committerCan Celik <ogulcancelik@gmail.com>2026-04-05 01:23:06 +0300
commit41dd733ca7123c9ac4bfc6c781697a660e95b052 (patch)
tree3fc91815ca139ef2e0b281bba0abda4ef15175f4 /vendor/libghostty-vt/src/shell-integration/zsh
parent55c138263e190a1e79d4b997bfb9397306c0ec1f (diff)
feat: finish ghostty pane backend integration
Diffstat (limited to 'vendor/libghostty-vt/src/shell-integration/zsh')
-rw-r--r--vendor/libghostty-vt/src/shell-integration/zsh/.zshenv61
-rw-r--r--vendor/libghostty-vt/src/shell-integration/zsh/ghostty-integration454
2 files changed, 515 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/shell-integration/zsh/.zshenv b/vendor/libghostty-vt/src/shell-integration/zsh/.zshenv
new file mode 100644
index 00000000..62dcf273
--- /dev/null
+++ b/vendor/libghostty-vt/src/shell-integration/zsh/.zshenv
@@ -0,0 +1,61 @@
+# Based on (started as) a copy of Kitty's zsh integration. Kitty is
+# distributed under GPLv3, so this file is also distributed under GPLv3.
+# The license header is reproduced below:
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+# This script is sourced automatically by zsh when ZDOTDIR is set to this
+# directory. It therefore assumes it's running within our shell integration
+# environment and should not be sourced manually (unlike ghostty-integration).
+#
+# This file can get sourced with aliases enabled. To avoid alias expansion
+# we quote everything that can be quoted. Some aliases will still break us
+# though.
+
+# Restore the original ZDOTDIR value if GHOSTTY_ZSH_ZDOTDIR is set.
+# Otherwise, unset the ZDOTDIR that was set during shell injection.
+if [[ -n "${GHOSTTY_ZSH_ZDOTDIR+X}" ]]; then
+ 'builtin' 'export' ZDOTDIR="$GHOSTTY_ZSH_ZDOTDIR"
+ 'builtin' 'unset' 'GHOSTTY_ZSH_ZDOTDIR'
+else
+ 'builtin' 'unset' 'ZDOTDIR'
+fi
+
+# Use try-always to have the right error code.
+{
+ # Zsh treats unset ZDOTDIR as if it was HOME. We do the same.
+ #
+ # Source the user's .zshenv before sourcing ghostty-integration because the
+ # former might set fpath and other things without which ghostty-integration
+ # won't work.
+ #
+ # Use typeset in case we are in a function with warn_create_global in
+ # effect. Unlikely but better safe than sorry.
+ 'builtin' 'typeset' _ghostty_file=${ZDOTDIR-$HOME}"/.zshenv"
+ # Zsh ignores unreadable rc files. We do the same.
+ # Zsh ignores rc files that are directories, and so does source.
+ [[ ! -r "$_ghostty_file" ]] || 'builtin' 'source' '--' "$_ghostty_file"
+} always {
+ if [[ -o 'interactive' ]]; then
+ # ${(%):-%x} is the path to the current file.
+ # On top of it we add :A:h to get the directory.
+ 'builtin' 'typeset' _ghostty_file="${${(%):-%x}:A:h}"/ghostty-integration
+ if [[ -r "$_ghostty_file" ]]; then
+ 'builtin' 'autoload' '-Uz' '--' "$_ghostty_file"
+ "${_ghostty_file:t}"
+ 'builtin' 'unfunction' '--' "${_ghostty_file:t}"
+ fi
+ fi
+ 'builtin' 'unset' '_ghostty_file'
+}
diff --git a/vendor/libghostty-vt/src/shell-integration/zsh/ghostty-integration b/vendor/libghostty-vt/src/shell-integration/zsh/ghostty-integration
new file mode 100644
index 00000000..76c5ce24
--- /dev/null
+++ b/vendor/libghostty-vt/src/shell-integration/zsh/ghostty-integration
@@ -0,0 +1,454 @@
+# vim:ft=zsh
+#
+# Based on (started as) a copy of Kitty's zsh integration. Kitty is
+# distributed under GPLv3, so this file is also distributed under GPLv3.
+# The license header is reproduced below:
+#
+# This program is free software: you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 3 of the License, or
+# (at your option) any later version.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program. If not, see <http://www.gnu.org/licenses/>.
+
+#
+# Enables integration between zsh and ghostty.
+#
+# This is an autoloadable function. It's invoked automatically in shells
+# directly spawned by Ghostty but not in any other shells. For example, running
+# `exec zsh`, `sudo -E zsh`, `tmux`, or plain `zsh` will create a shell where
+# ghostty-integration won't automatically run. Zsh users who want integration with
+# Ghostty in all shells should add the following lines to their .zshrc:
+#
+# if [[ -n $GHOSTTY_RESOURCES_DIR ]]; then
+# source "$GHOSTTY_RESOURCES_DIR"/shell-integration/zsh/ghostty-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.
+
+# Note that updating options with `builtin emulate -L zsh` affects the global options
+# if it's called outside of a function. So nearly all code has to be in functions.
+_entrypoint() {
+ builtin emulate -L zsh -o no_warn_create_global -o no_aliases
+
+ [[ -o interactive ]] || builtin return 0 # non-interactive shell
+ (( ! $+_ghostty_state )) || builtin return 0 # already initialized
+
+ # We require zsh 5.1+ (released Sept 2015) for features like functions_source,
+ # introspection arrays, and array pattern substitution.
+ if ! { builtin autoload -- is-at-least 2>/dev/null && is-at-least 5.1; }; then
+ builtin echo "Zsh ${ZSH_VERSION} is too old for ghostty shell integration (5.1+ required)" >&2
+ builtin return 1
+ fi
+
+ # 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 _ghostty_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 {_ghostty_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 _ghostty_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 _ghostty_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
+ # _ghostty_deferred_init.
+ typeset -gi _ghostty_fd
+ {
+ builtin zmodload zsh/system && (( $+builtins[sysopen] )) && {
+ { [[ -w $TTY ]] && builtin sysopen -o cloexec -wu _ghostty_fd -- $TTY } ||
+ { [[ -w /dev/tty ]] && builtin sysopen -o cloexec -wu _ghostty_fd -- /dev/tty }
+ }
+ } 2>/dev/null || (( _ghostty_fd = 1 ))
+
+ # Defer initialization so that other zsh init files can be configure
+ # the integration.
+ builtin typeset -ag precmd_functions
+ precmd_functions+=(_ghostty_deferred_init)
+}
+
+_ghostty_deferred_init() {
+ builtin emulate -L zsh -o no_warn_create_global -o no_aliases
+
+ # Enable semantic markup with OSC 133.
+ _ghostty_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 (( _ghostty_state == 1 )); then
+ # The last written OSC 133 C has not been closed with D yet.
+ # Close it and supply status.
+ builtin print -nu $_ghostty_fd '\e]133;D;'$cmd_status'\a'
+ (( _ghostty_state = 2 ))
+ elif (( _ghostty_state == 2 )); then
+ # There might be an unclosed OSC 133 C. Close that.
+ builtin print -nu $_ghostty_fd '\e]133;D\a'
+ fi
+ fi
+
+ builtin local mark1=$'%{\e]133;A;cl=line\a%}'
+ if [[ -o prompt_percent ]]; then
+ builtin typeset -g precmd_functions
+ if [[ ${precmd_functions[-1]} == _ghostty_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.
+
+ # Restore PS1/PS2 to their pre-mark state if nothing else has
+ # modified them since we last added marks. This avoids exposing
+ # PS1 with our marks to other hooks (which can break themes like
+ # Pure that use pattern matching to strip/rebuild the prompt).
+ # If PS1 was modified (by a theme, async update, etc.), we
+ # keep the modified version, prioritizing the theme's changes.
+ builtin local ps1_changed=0
+ if [[ -n ${_ghostty_saved_ps1+x} ]]; then
+ if [[ $PS1 == $_ghostty_marked_ps1 ]]; then
+ PS1=$_ghostty_saved_ps1
+ PS2=$_ghostty_saved_ps2
+ elif [[ $PS1 != $_ghostty_saved_ps1 ]]; then
+ ps1_changed=1
+ fi
+ fi
+
+ # Save the clean PS1/PS2 before we add marks.
+ _ghostty_saved_ps1=$PS1
+ _ghostty_saved_ps2=$PS2
+
+ # Add our marks. Since we always start from a clean PS1
+ # (either restored above or freshly set by a theme), we can
+ # unconditionally add mark1 and markB.
+ builtin local mark2=$'%{\e]133;P;k=s\a%}'
+ builtin local markB=$'%{\e]133;B\a%}'
+ # If PS1 ends with a bare '%', it combines with the '{'
+ # in markB to form a '%{' prompt escape, swallowing the
+ # marker and producing a visible '{'. Fix by doubling the
+ # trailing '%' so it becomes a literal '%%'.
+ [[ $PS1 == *[^%]% || $PS1 == % ]] && PS1=$PS1%
+ PS1=${mark1}${PS1}${markB}
+
+ # Handle multiline prompts by marking newline-separated
+ # continuation lines with k=s (mark2).
+ #
+ # We skip this when PS1 changed because injecting marks into
+ # newlines can break pattern matching in themes that
+ # strip/rebuild the prompt dynamically (e.g., Pure).
+ if (( ! ps1_changed )) && [[ $PS1 == *$'\n'* ]]; then
+ PS1=${PS1//$'\n'/$'\n'${mark2}}
+ fi
+
+ # PS2 mark is needed when clearing the prompt on resize
+ [[ $PS2 == *[^%]% || $PS2 == % ]] && PS2=$PS2%
+ PS2=${mark2}${PS2}${markB}
+
+ # Save the marked PS1 so we can detect modifications
+ # by other hooks in the next cycle.
+ _ghostty_marked_ps1=$PS1
+ (( _ghostty_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:#_ghostty_precmd} _ghostty_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 $_ghostty_fd -- $mark1[3,-3]
+ (( _ghostty_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 $_ghostty_fd -- $mark1[3,-3]
+ (( _ghostty_state = 2 ))
+ fi
+ }
+
+ _ghostty_preexec() {
+ builtin emulate -L zsh -o no_warn_create_global -o no_aliases
+
+ # Restore the original PS1/PS2 if nothing else has modified them
+ # since our precmd added marks. This ensures other preexec hooks
+ # see a clean PS1 without our marks. If PS1 was modified (e.g.,
+ # by an async theme update), we leave it alone.
+ if [[ -n ${_ghostty_saved_ps1+x} && $PS1 == $_ghostty_marked_ps1 ]]; then
+ PS1=$_ghostty_saved_ps1
+ PS2=$_ghostty_saved_ps2
+ fi
+
+ # 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 $_ghostty_fd '\e]133;C\a'
+ (( _ghostty_state = 1 ))
+ }
+
+ # Enable reporting current working dir to terminal. Ghostty supports
+ # the kitty-shell-cwd format.
+ _ghostty_report_pwd() { builtin print -nu $_ghostty_fd '\e]7;kitty-shell-cwd://'"$HOST""$PWD"'\a'; }
+ chpwd_functions=(${chpwd_functions[@]} "_ghostty_report_pwd")
+ # An executed program could change cwd and report the changed cwd, so also report cwd at each new prompt
+ # as in this case chpwd_functions is insufficient. chpwd_functions is still needed for things like: cd x && something
+ functions[_ghostty_precmd]+="
+ _ghostty_report_pwd"
+ _ghostty_report_pwd
+
+ if [[ "$GHOSTTY_SHELL_FEATURES" == *"title"* ]]; then
+ # Enable terminal title changes, formatted for user-friendly display.
+ functions[_ghostty_precmd]+="
+ builtin print -rnu $_ghostty_fd \$'\\e]2;'\"\${(%):-%(4~|…/%3~|%~)}\"\$'\\a'"
+ functions[_ghostty_preexec]+="
+ builtin print -rnu $_ghostty_fd \$'\\e]2;'\"\${1//[[:cntrl:]]}\"\$'\\a'"
+ fi
+
+ if [[ "$GHOSTTY_SHELL_FEATURES" == *"cursor"* ]]; then
+ # Enable cursor shape changes depending on the current keymap.
+ # 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.
+ _ghostty_zle_line_init _ghostty_zle_line_finish _ghostty_zle_keymap_select() {
+ 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 default shape before executing an external command
+ functions[_ghostty_preexec]+="
+ builtin print -rnu $_ghostty_fd \$'\\e[0 q'"
+ fi
+
+ # Emit semantic prompt markers at line-init if PS1 doesn't contain our
+ # marks. This ensures the terminal sees prompt markers even if another
+ # plugin (like zinit or oh-my-posh) regenerated PS1 after our precmd ran.
+ # We use 133;P instead of 133;A to avoid fresh-line behavior which would
+ # disrupt the display since the prompt has already been drawn. We also
+ # emit 133;B to mark the input area, which is needed for click-to-move.
+ (( $+functions[_ghostty_zle_line_init] )) || _ghostty_zle_line_init() { builtin true; }
+ functions[_ghostty_zle_line_init]="
+ if [[ \$PS1 != *$'%{\\e]133;A'* ]]; then
+ builtin print -nu \$_ghostty_fd '\\e]133;P;k=i\\a\\e]133;B\\a'
+ fi
+ "${functions[_ghostty_zle_line_init]}
+
+ # Add Ghostty binary to PATH if the path feature is enabled
+ if [[ "$GHOSTTY_SHELL_FEATURES" == *"path"* ]] && [[ -n "$GHOSTTY_BIN_DIR" ]]; then
+ if [[ ":$PATH:" != *":$GHOSTTY_BIN_DIR:"* ]]; then
+ builtin export PATH="$PATH:$GHOSTTY_BIN_DIR"
+ fi
+ fi
+
+ # Sudo
+ if [[ "$GHOSTTY_SHELL_FEATURES" == *"sudo"* ]] && [[ -n "$TERMINFO" ]]; then
+ # Wrap `sudo` command to ensure Ghostty terminfo is preserved
+ function sudo() {
+ builtin local sudo_has_sudoedit_flags="no"
+ for arg in "$@"; do
+ # Check if argument is '-e' or '--edit' (sudoedit flags)
+ if [[ "$arg" == "-e" || $arg == "--edit" ]]; then
+ sudo_has_sudoedit_flags="yes"
+ builtin break
+ fi
+ # Check if argument is neither an option nor a key-value pair
+ if [[ "$arg" != -* && "$arg" != *=* ]]; then
+ builtin break
+ fi
+ done
+ if [[ "$sudo_has_sudoedit_flags" == "yes" ]]; then
+ builtin command sudo "$@";
+ else
+ builtin command sudo --preserve-env=TERMINFO "$@";
+ fi
+ }
+ fi
+
+ # SSH Integration
+ if [[ "$GHOSTTY_SHELL_FEATURES" == *ssh-* ]]; then
+ function ssh() {
+ emulate -L zsh
+ setopt local_options no_glob_subst
+
+ local ssh_term ssh_opts
+ ssh_term="xterm-256color"
+ ssh_opts=()
+
+ # Configure environment variables for remote session
+ if [[ "$GHOSTTY_SHELL_FEATURES" == *ssh-env* ]]; then
+ ssh_opts+=(-o "SendEnv COLORTERM TERM_PROGRAM TERM_PROGRAM_VERSION")
+ fi
+
+ # Install terminfo on remote host if needed
+ if [[ "$GHOSTTY_SHELL_FEATURES" == *ssh-terminfo* ]]; then
+ local ssh_user ssh_hostname
+
+ while IFS=' ' read -r ssh_key ssh_value; do
+ case "$ssh_key" in
+ user) ssh_user="$ssh_value" ;;
+ hostname) ssh_hostname="$ssh_value" ;;
+ esac
+ [[ -n "$ssh_user" && -n "$ssh_hostname" ]] && break
+ done < <(command ssh -G "$@" 2>/dev/null)
+
+ if [[ -n "$ssh_hostname" ]]; then
+ local ssh_target="${ssh_user}@${ssh_hostname}"
+
+ # Check if terminfo is already cached
+ if "$GHOSTTY_BIN_DIR/ghostty" +ssh-cache --host="$ssh_target" >/dev/null 2>&1; then
+ ssh_term="xterm-ghostty"
+ elif (( $+commands[infocmp] )); then
+ local ssh_terminfo ssh_cpath_dir ssh_cpath
+
+ ssh_terminfo=$(infocmp -0 -x xterm-ghostty 2>/dev/null)
+
+ if [[ -n "$ssh_terminfo" ]]; then
+ print "Setting up xterm-ghostty terminfo on $ssh_hostname..." >&2
+
+ ssh_cpath_dir=$(mktemp -d "/tmp/ghostty-ssh-$ssh_user.XXXXXX" 2>/dev/null) || ssh_cpath_dir="/tmp/ghostty-ssh-$ssh_user.$$"
+ ssh_cpath="$ssh_cpath_dir/socket"
+
+ if builtin print -r "$ssh_terminfo" | command ssh "${ssh_opts[@]}" -o ControlMaster=yes -o ControlPath="$ssh_cpath" -o ControlPersist=60s "$@" '
+ infocmp xterm-ghostty >/dev/null 2>&1 && exit 0
+ command -v tic >/dev/null 2>&1 || exit 1
+ mkdir -p ~/.terminfo 2>/dev/null && tic -x - 2>/dev/null && exit 0
+ exit 1
+ ' 2>/dev/null; then
+ ssh_term="xterm-ghostty"
+ ssh_opts+=(-o "ControlPath=$ssh_cpath")
+
+ # Cache successful installation
+ "$GHOSTTY_BIN_DIR/ghostty" +ssh-cache --add="$ssh_target" >/dev/null 2>&1 || true
+ else
+ print "Warning: Failed to install terminfo." >&2
+ fi
+ else
+ print "Warning: Could not generate terminfo data." >&2
+ fi
+ else
+ print "Warning: ghostty command not available for cache management." >&2
+ fi
+ fi
+ fi
+
+ # Execute SSH with TERM environment variable
+ TERM="$ssh_term" COLORTERM=truecolor command ssh "${ssh_opts[@]}" "$@"
+ }
+ 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=_ghostty_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=._ghostty_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[_ghostty_preexec] )); then
+ builtin typeset -ag preexec_functions
+ preexec_functions+=(_ghostty_preexec)
+ fi
+
+ builtin typeset -ag precmd_functions
+ if (( $+functions[_ghostty_precmd] )); then
+ precmd_functions=(${precmd_functions:#_ghostty_deferred_init} _ghostty_precmd)
+ _ghostty_precmd
+ else
+ precmd_functions=(${precmd_functions:#_ghostty_deferred_init})
+ fi
+
+ # Unfunction _ghostty_deferred_init to save memory. Don't unfunction
+ # ghostty-integration though because decent public functions aren't supposed to
+ # to unfunction themselves when invoked. Unfunctioning is done by calling code.
+ builtin unfunction _ghostty_deferred_init
+}
+
+_entrypoint