From 0610f4aeab539b079007f973dfe7d2d0a7fb7b42 Mon Sep 17 00:00:00 2001 From: Arctic Date: Fri, 4 Jul 2025 11:48:40 -0500 Subject: [PATCH] first commit --- .gitattributes | 1 + .gitignore | 2 + .tmux.conf | 14 + .zshenv | 45 + .zshrc | 100 ++ .zshrc.mac | 97 ++ fn/-z4h-autosuggest-fetch | 34 + fn/-z4h-cd-rotate | 11 + fn/-z4h-check-rc-zwcs | 25 + fn/-z4h-chsh | 123 ++ fn/-z4h-cmd-bindkey | 54 + fn/-z4h-cmd-compile | 9 + fn/-z4h-cmd-docker | 7 + fn/-z4h-cmd-help | 54 + fn/-z4h-cmd-ssh | 519 +++++++ fn/-z4h-cmd-sudo | 7 + fn/-z4h-cmd-tty-wait | 32 + fn/-z4h-cmd-update | 82 ++ fn/-z4h-command-not-found | 36 + fn/-z4h-comp-files | 148 ++ fn/-z4h-comp-insert-all | 44 + fn/-z4h-comp-words | 125 ++ fn/-z4h-compile | 41 + fn/-z4h-compinit | 107 ++ fn/-z4h-complete-bw | 29 + fn/-z4h-complete-cargo | 13 + fn/-z4h-complete-gh | 28 + fn/-z4h-complete-helm | 32 + fn/-z4h-complete-kitty | 32 + fn/-z4h-complete-kubectl | 41 + fn/-z4h-complete-oc | 41 + fn/-z4h-complete-rustup | 13 + fn/-z4h-direnv-hook | 81 ++ fn/-z4h-direnv-init | 32 + fn/-z4h-enable-iterm2-integration | 47 + fn/-z4h-error-command | 37 + fn/-z4h-error-iterm2-integration | 23 + fn/-z4h-error-param-changed | 19 + fn/-z4h-find | 58 + fn/-z4h-find-prev-zword | 21 + fn/-z4h-fix-locale | 7 + fn/-z4h-flowing | 30 + fn/-z4h-fzf | 64 + fn/-z4h-gen-init-darwin-paths | 32 + fn/-z4h-get-cursor-pos | 24 + fn/-z4h-help-bindkey | 8 + fn/-z4h-help-compile | 36 + fn/-z4h-help-load | 25 + fn/-z4h-help-source | 41 + fn/-z4h-help-ssh | 22 + fn/-z4h-init | 136 ++ fn/-z4h-init-wsl | 23 + fn/-z4h-init-zle | 1103 +++++++++++++++ fn/-z4h-insert-all | 19 + fn/-z4h-install-many | 125 ++ fn/-z4h-install-one | 150 ++ fn/-z4h-is-valid-list | 23 + fn/-z4h-main-complete | 120 ++ fn/-z4h-move-and-kill | 26 + fn/-z4h-mv | 21 + fn/-z4h-osc9 | 35 + fn/-z4h-postinstall-forgit | 19 + fn/-z4h-postinstall-fz | 18 + fn/-z4h-postinstall-fzf | 59 + fn/-z4h-postinstall-ohmyzsh | 5 + fn/-z4h-postinstall-powerlevel10k | 21 + fn/-z4h-postinstall-self | 14 + fn/-z4h-postinstall-systemd | 30 + fn/-z4h-postinstall-terminfo | 27 + fn/-z4h-postinstall-zsh-autosuggestions | 5 + ...h-postinstall-zsh-history-substring-search | 5 + fn/-z4h-postinstall-zsh-syntax-highlighting | 5 + fn/-z4h-postinstall-zsh-users | 19 + fn/-z4h-postinstall-zsh-you-should-use | 25 + fn/-z4h-present-files | 148 ++ fn/-z4h-prompt-length | 15 + fn/-z4h-read-dir-history | 9 + fn/-z4h-redraw-buffer | 13 + fn/-z4h-redraw-prompt | 18 + fn/-z4h-replace-buf | 40 + fn/-z4h-restore-screen | 14 + fn/-z4h-run-process-tree | 42 + fn/-z4h-sanitize-word-prefix | 12 + fn/-z4h-save-screen | 13 + fn/-z4h-set-list-colors | 57 + fn/-z4h-set-term-title | 14 + fn/-z4h-show-dots | 27 + fn/-z4h-ssh-maybe-update | 18 + fn/-z4h-start-ssh-agent | 25 + fn/-z4h-string-diff | 84 ++ fn/-z4h-tmux-bypass | 12 + fn/-z4h-update-dir-history | 52 + fn/-z4h-vte-osc7 | 26 + fn/-z4h-welcome | 96 ++ fn/-z4h-with-local-history | 13 + fn/-z4h-write-dir-history | 6 + fn/-z4h-zle-line-finish | 26 + fn/-z4h-zle-line-init | 39 + fn/-z4h-zle-line-pre-redraw | 40 + fn/_z4h_cursor_max | 9 + fn/_z4h_err | 8 + fn/bracketed-paste-magic | 232 ++++ fn/notes.md | 1211 +++++++++++++++++ fn/ssh-teleportation.asciinema | 162 +++ fn/z4h-accept-line | 10 + fn/z4h-autosuggest-accept | 9 + fn/z4h-backward-word | 19 + fn/z4h-backward-zword | 14 + fn/z4h-cd-down | 149 ++ fn/z4h-clear-screen-hard-bottom | 10 + fn/z4h-clear-screen-hard-top | 10 + fn/z4h-clear-screen-soft-bottom | 11 + fn/z4h-clear-screen-soft-top | 11 + fn/z4h-eof | 10 + fn/z4h-exit | 11 + fn/z4h-forward-word | 19 + fn/z4h-forward-zword | 16 + fn/z4h-fzf-complete | 281 ++++ fn/z4h-fzf-dir-history | 176 +++ fn/z4h-fzf-history | 170 +++ fn/z4h-quote-prev-zword | 73 + fn/z4h-stash-buffer | 6 + main.zsh | 480 +++++++ sc/exec-zsh-i | 115 ++ sc/install-tmux | 521 +++++++ sc/setup | 60 + sc/ssh-bootstrap | 262 ++++ version | 1 + z4h.zsh | 376 +++++ zb/zsh | 10 + 130 files changed, 9897 insertions(+) create mode 100644 .gitattributes create mode 100644 .gitignore create mode 100644 .tmux.conf create mode 100644 .zshenv create mode 100644 .zshrc create mode 100644 .zshrc.mac create mode 100644 fn/-z4h-autosuggest-fetch create mode 100644 fn/-z4h-cd-rotate create mode 100644 fn/-z4h-check-rc-zwcs create mode 100644 fn/-z4h-chsh create mode 100644 fn/-z4h-cmd-bindkey create mode 100644 fn/-z4h-cmd-compile create mode 100644 fn/-z4h-cmd-docker create mode 100644 fn/-z4h-cmd-help create mode 100644 fn/-z4h-cmd-ssh create mode 100644 fn/-z4h-cmd-sudo create mode 100644 fn/-z4h-cmd-tty-wait create mode 100644 fn/-z4h-cmd-update create mode 100644 fn/-z4h-command-not-found create mode 100644 fn/-z4h-comp-files create mode 100644 fn/-z4h-comp-insert-all create mode 100644 fn/-z4h-comp-words create mode 100644 fn/-z4h-compile create mode 100644 fn/-z4h-compinit create mode 100644 fn/-z4h-complete-bw create mode 100644 fn/-z4h-complete-cargo create mode 100644 fn/-z4h-complete-gh create mode 100644 fn/-z4h-complete-helm create mode 100644 fn/-z4h-complete-kitty create mode 100644 fn/-z4h-complete-kubectl create mode 100644 fn/-z4h-complete-oc create mode 100644 fn/-z4h-complete-rustup create mode 100644 fn/-z4h-direnv-hook create mode 100644 fn/-z4h-direnv-init create mode 100644 fn/-z4h-enable-iterm2-integration create mode 100644 fn/-z4h-error-command create mode 100644 fn/-z4h-error-iterm2-integration create mode 100644 fn/-z4h-error-param-changed create mode 100644 fn/-z4h-find create mode 100644 fn/-z4h-find-prev-zword create mode 100644 fn/-z4h-fix-locale create mode 100644 fn/-z4h-flowing create mode 100644 fn/-z4h-fzf create mode 100644 fn/-z4h-gen-init-darwin-paths create mode 100644 fn/-z4h-get-cursor-pos create mode 100644 fn/-z4h-help-bindkey create mode 100644 fn/-z4h-help-compile create mode 100644 fn/-z4h-help-load create mode 100644 fn/-z4h-help-source create mode 100644 fn/-z4h-help-ssh create mode 100644 fn/-z4h-init create mode 100644 fn/-z4h-init-wsl create mode 100644 fn/-z4h-init-zle create mode 100644 fn/-z4h-insert-all create mode 100644 fn/-z4h-install-many create mode 100644 fn/-z4h-install-one create mode 100644 fn/-z4h-is-valid-list create mode 100644 fn/-z4h-main-complete create mode 100644 fn/-z4h-move-and-kill create mode 100644 fn/-z4h-mv create mode 100644 fn/-z4h-osc9 create mode 100644 fn/-z4h-postinstall-forgit create mode 100644 fn/-z4h-postinstall-fz create mode 100644 fn/-z4h-postinstall-fzf create mode 100644 fn/-z4h-postinstall-ohmyzsh create mode 100644 fn/-z4h-postinstall-powerlevel10k create mode 100644 fn/-z4h-postinstall-self create mode 100644 fn/-z4h-postinstall-systemd create mode 100644 fn/-z4h-postinstall-terminfo create mode 100644 fn/-z4h-postinstall-zsh-autosuggestions create mode 100644 fn/-z4h-postinstall-zsh-history-substring-search create mode 100644 fn/-z4h-postinstall-zsh-syntax-highlighting create mode 100644 fn/-z4h-postinstall-zsh-users create mode 100644 fn/-z4h-postinstall-zsh-you-should-use create mode 100644 fn/-z4h-present-files create mode 100644 fn/-z4h-prompt-length create mode 100644 fn/-z4h-read-dir-history create mode 100644 fn/-z4h-redraw-buffer create mode 100644 fn/-z4h-redraw-prompt create mode 100644 fn/-z4h-replace-buf create mode 100644 fn/-z4h-restore-screen create mode 100644 fn/-z4h-run-process-tree create mode 100644 fn/-z4h-sanitize-word-prefix create mode 100644 fn/-z4h-save-screen create mode 100644 fn/-z4h-set-list-colors create mode 100644 fn/-z4h-set-term-title create mode 100644 fn/-z4h-show-dots create mode 100644 fn/-z4h-ssh-maybe-update create mode 100644 fn/-z4h-start-ssh-agent create mode 100644 fn/-z4h-string-diff create mode 100644 fn/-z4h-tmux-bypass create mode 100644 fn/-z4h-update-dir-history create mode 100644 fn/-z4h-vte-osc7 create mode 100644 fn/-z4h-welcome create mode 100644 fn/-z4h-with-local-history create mode 100644 fn/-z4h-write-dir-history create mode 100644 fn/-z4h-zle-line-finish create mode 100644 fn/-z4h-zle-line-init create mode 100644 fn/-z4h-zle-line-pre-redraw create mode 100644 fn/_z4h_cursor_max create mode 100644 fn/_z4h_err create mode 100644 fn/bracketed-paste-magic create mode 100644 fn/notes.md create mode 100644 fn/ssh-teleportation.asciinema create mode 100644 fn/z4h-accept-line create mode 100644 fn/z4h-autosuggest-accept create mode 100644 fn/z4h-backward-word create mode 100644 fn/z4h-backward-zword create mode 100644 fn/z4h-cd-down create mode 100644 fn/z4h-clear-screen-hard-bottom create mode 100644 fn/z4h-clear-screen-hard-top create mode 100644 fn/z4h-clear-screen-soft-bottom create mode 100644 fn/z4h-clear-screen-soft-top create mode 100644 fn/z4h-eof create mode 100644 fn/z4h-exit create mode 100644 fn/z4h-forward-word create mode 100644 fn/z4h-forward-zword create mode 100644 fn/z4h-fzf-complete create mode 100644 fn/z4h-fzf-dir-history create mode 100644 fn/z4h-fzf-history create mode 100644 fn/z4h-quote-prev-zword create mode 100644 fn/z4h-stash-buffer create mode 100644 main.zsh create mode 100644 sc/exec-zsh-i create mode 100755 sc/install-tmux create mode 100755 sc/setup create mode 100755 sc/ssh-bootstrap create mode 100644 version create mode 100644 z4h.zsh create mode 100755 zb/zsh diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..fcadb2c --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* text eol=lf diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..f884d57 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +*.zwc +/fn/-z4h-compinit-impl diff --git a/.tmux.conf b/.tmux.conf new file mode 100644 index 0000000..09e68dd --- /dev/null +++ b/.tmux.conf @@ -0,0 +1,14 @@ +unbind -a +set -g prefix None +set -g prefix2 None +set -g escape-time 1 +set -g status off +set -g automatic-rename off +set -g set-titles on +set -g set-titles-string "#T" +set -g history-limit 0 +set -g message-limit 0 +set -g assume-paste-time 0 +set -ga update-environment ' VTE_VERSION KITTY_LISTEN_ON GUAKE_TAB_UUID NVIM NVIM_LISTEN_ADDRESS VIMRUNTIME VIM _Z4H_LINES _Z4H_COLUMNS _Z4H_ORIG_CWD' +set -s set-clipboard on +set -as terminal-overrides ',*:Ms=\E]52;%p1%s;%p2%s\007' diff --git a/.zshenv b/.zshenv new file mode 100644 index 0000000..f7ffaec --- /dev/null +++ b/.zshenv @@ -0,0 +1,45 @@ +# Documentation: https://github.com/romkatv/zsh4humans/blob/v5/README.md. +# +# Do not modify this file unless you know exactly what you are doing. +# It is strongly recommended to keep all shell customization and configuration +# (including exported environment variables such as PATH) in ~/.zshrc or in +# files sourced from ~/.zshrc. If you are certain that you must export some +# environment variables in ~/.zshenv, do it where indicated by comments below. + +if [ -n "${ZSH_VERSION-}" ]; then + # If you are certain that you must export some environment variables + # in ~/.zshenv (see comments at the top!), do it here: + # + # export GOPATH=$HOME/go + # + # Do not change anything else in this file. + + : ${ZDOTDIR:=~} + setopt no_global_rcs + [[ -o no_interactive && -z "${Z4H_BOOTSTRAPPING-}" ]] && return + setopt no_rcs + unset Z4H_BOOTSTRAPPING +fi + +Z4H_URL="https://raw.githubusercontent.com/romkatv/zsh4humans/v5" +: "${Z4H:=${XDG_CACHE_HOME:-$HOME/.cache}/zsh4humans/v5}" + +umask o-w + +if [ ! -e "$Z4H"/z4h.zsh ]; then + mkdir -p -- "$Z4H" || return + >&2 printf '\033[33mz4h\033[0m: fetching \033[4mz4h.zsh\033[0m\n' + if command -v curl >/dev/null 2>&1; then + curl -fsSL -- "$Z4H_URL"/z4h.zsh >"$Z4H"/z4h.zsh.$$ || return + elif command -v wget >/dev/null 2>&1; then + wget -O- -- "$Z4H_URL"/z4h.zsh >"$Z4H"/z4h.zsh.$$ || return + else + >&2 printf '\033[33mz4h\033[0m: please install \033[32mcurl\033[0m or \033[32mwget\033[0m\n' + return 1 + fi + mv -- "$Z4H"/z4h.zsh.$$ "$Z4H"/z4h.zsh || return +fi + +. "$Z4H"/z4h.zsh || return + +setopt rcs diff --git a/.zshrc b/.zshrc new file mode 100644 index 0000000..fbe3932 --- /dev/null +++ b/.zshrc @@ -0,0 +1,100 @@ +# Personal Zsh configuration file. It is strongly recommended to keep all +# shell customization and configuration (including exported environment +# variables such as PATH) in this file or in files sourced from it. +# +# Documentation: https://github.com/romkatv/zsh4humans/blob/v5/README.md. + +# Periodic auto-update on Zsh startup: 'ask' or 'no'. +# You can manually run `z4h update` to update everything. +zstyle ':z4h:' auto-update 'no' +# Ask whether to auto-update this often; has no effect if auto-update is 'no'. +zstyle ':z4h:' auto-update-days '28' + +# Keyboard type: 'mac' or 'pc'. +zstyle ':z4h:bindkey' keyboard 'pc' + +# Mark up shell's output with semantic information. +zstyle ':z4h:' term-shell-integration 'yes' + +# Right-arrow key accepts one character ('partial-accept') from +# command autosuggestions or the whole thing ('accept')? +zstyle ':z4h:autosuggestions' forward-char 'accept' + +# Recursively traverse directories when TAB-completing files. +zstyle ':z4h:fzf-complete' recurse-dirs 'no' + +# Enable direnv to automatically source .envrc files. +zstyle ':z4h:direnv' enable 'no' +# Show "loading" and "unloading" notifications from direnv. +zstyle ':z4h:direnv:success' notify 'yes' + +# Enable ('yes') or disable ('no') automatic teleportation of z4h over +# SSH when connecting to these hosts. +zstyle ':z4h:ssh:example-hostname1' enable 'yes' +zstyle ':z4h:ssh:*.example-hostname2' enable 'no' +# The default value if none of the overrides above match the hostname. +zstyle ':z4h:ssh:*' enable 'no' + +# Send these files over to the remote host when connecting over SSH to the +# enabled hosts. +zstyle ':z4h:ssh:*' send-extra-files '~/.nanorc' '~/.env.zsh' + +# Clone additional Git repositories from GitHub. +# +# This doesn't do anything apart from cloning the repository and keeping it +# up-to-date. Cloned files can be used after `z4h init`. This is just an +# example. If you don't plan to use Oh My Zsh, delete this line. +z4h install ohmyzsh/ohmyzsh || return + +# Install or update core components (fzf, zsh-autosuggestions, etc.) and +# initialize Zsh. After this point console I/O is unavailable until Zsh +# is fully initialized. Everything that requires user interaction or can +# perform network I/O must be done above. Everything else is best done below. +z4h init || return + +# Extend PATH. +path=(~/bin $path) + +# Export environment variables. +export GPG_TTY=$TTY + +# Source additional local files if they exist. +z4h source ~/.env.zsh + +# Use additional Git repositories pulled in with `z4h install`. +# +# This is just an example that you should delete. It does nothing useful. +z4h source ohmyzsh/ohmyzsh/lib/diagnostics.zsh # source an individual file +z4h load ohmyzsh/ohmyzsh/plugins/emoji-clock # load a plugin + +# Define key bindings. +z4h bindkey z4h-backward-kill-word Ctrl+Backspace Ctrl+H +z4h bindkey z4h-backward-kill-zword Ctrl+Alt+Backspace + +z4h bindkey undo Ctrl+/ Shift+Tab # undo the last command line change +z4h bindkey redo Alt+/ # redo the last undone command line change + +z4h bindkey z4h-cd-back Alt+Left # cd into the previous directory +z4h bindkey z4h-cd-forward Alt+Right # cd into the next directory +z4h bindkey z4h-cd-up Alt+Up # cd into the parent directory +z4h bindkey z4h-cd-down Alt+Down # cd into a child directory + +# Autoload functions. +autoload -Uz zmv + +# Define functions and completions. +function md() { [[ $# == 1 ]] && mkdir -p -- "$1" && cd -- "$1" } +compdef _directories md + +# Define named directories: ~w <=> Windows home directory on WSL. +[[ -z $z4h_win_home ]] || hash -d w=$z4h_win_home + +# Define aliases. +alias tree='tree -a -I .git' + +# Add flags to existing aliases. +alias ls="${aliases[ls]:-ls} -A" + +# Set shell options: http://zsh.sourceforge.net/Doc/Release/Options.html. +setopt glob_dots # no special treatment for file names with a leading dot +setopt no_auto_menu # require an extra TAB press to open the completion menu diff --git a/.zshrc.mac b/.zshrc.mac new file mode 100644 index 0000000..5a20599 --- /dev/null +++ b/.zshrc.mac @@ -0,0 +1,97 @@ +# Personal Zsh configuration file. It is strongly recommended to keep all +# shell customization and configuration (including exported environment +# variables such as PATH) in this file or in files sourced from it. +# +# Documentation: https://github.com/romkatv/zsh4humans/blob/v5/README.md. + +# Periodic auto-update on Zsh startup: 'ask' or 'no'. +# You can manually run `z4h update` to update everything. +zstyle ':z4h:' auto-update 'no' +# Ask whether to auto-update this often; has no effect if auto-update is 'no'. +zstyle ':z4h:' auto-update-days '28' + +# Keyboard type: 'mac' or 'pc'. +zstyle ':z4h:bindkey' keyboard 'mac' + +# Mark up shell's output with semantic information. +zstyle ':z4h:' term-shell-integration 'yes' + +# Right-arrow key accepts one character ('partial-accept') from +# command autosuggestions or the whole thing ('accept')? +zstyle ':z4h:autosuggestions' forward-char 'accept' + +# Recursively traverse directories when TAB-completing files. +zstyle ':z4h:fzf-complete' recurse-dirs 'no' + +# Enable direnv to automatically source .envrc files. +zstyle ':z4h:direnv' enable 'no' +# Show "loading" and "unloading" notifications from direnv. +zstyle ':z4h:direnv:success' notify 'yes' + +# Enable ('yes') or disable ('no') automatic teleportation of z4h over +# SSH when connecting to these hosts. +zstyle ':z4h:ssh:example-hostname1' enable 'yes' +zstyle ':z4h:ssh:*.example-hostname2' enable 'no' +# The default value if none of the overrides above match the hostname. +zstyle ':z4h:ssh:*' enable 'no' + +# Send these files over to the remote host when connecting over SSH to the +# enabled hosts. +zstyle ':z4h:ssh:*' send-extra-files '~/.nanorc' '~/.env.zsh' + +# Clone additional Git repositories from GitHub. +# +# This doesn't do anything apart from cloning the repository and keeping it +# up-to-date. Cloned files can be used after `z4h init`. This is just an +# example. If you don't plan to use Oh My Zsh, delete this line. +z4h install ohmyzsh/ohmyzsh || return + +# Install or update core components (fzf, zsh-autosuggestions, etc.) and +# initialize Zsh. After this point console I/O is unavailable until Zsh +# is fully initialized. Everything that requires user interaction or can +# perform network I/O must be done above. Everything else is best done below. +z4h init || return + +# Extend PATH. +path=(~/bin $path) + +# Export environment variables. +export GPG_TTY=$TTY + +# Source additional local files if they exist. +z4h source ~/.env.zsh + +# Use additional Git repositories pulled in with `z4h install`. +# +# This is just an example that you should delete. It does nothing useful. +z4h source ohmyzsh/ohmyzsh/lib/diagnostics.zsh # source an individual file +z4h load ohmyzsh/ohmyzsh/plugins/emoji-clock # load a plugin + +# Define key bindings. +z4h bindkey undo Ctrl+/ Shift+Tab # undo the last command line change +z4h bindkey redo Option+/ # redo the last undone command line change + +z4h bindkey z4h-cd-back Shift+Left # cd into the previous directory +z4h bindkey z4h-cd-forward Shift+Right # cd into the next directory +z4h bindkey z4h-cd-up Shift+Up # cd into the parent directory +z4h bindkey z4h-cd-down Shift+Down # cd into a child directory + +# Autoload functions. +autoload -Uz zmv + +# Define functions and completions. +function md() { [[ $# == 1 ]] && mkdir -p -- "$1" && cd -- "$1" } +compdef _directories md + +# Define named directories: ~w <=> Windows home directory on WSL. +[[ -z $z4h_win_home ]] || hash -d w=$z4h_win_home + +# Define aliases. +alias tree='tree -a -I .git' + +# Add flags to existing aliases. +alias ls="${aliases[ls]:-ls} -A" + +# Set shell options: http://zsh.sourceforge.net/Doc/Release/Options.html. +setopt glob_dots # no special treatment for file names with a leading dot +setopt no_auto_menu # require an extra TAB press to open the completion menu diff --git a/fn/-z4h-autosuggest-fetch b/fn/-z4h-autosuggest-fetch new file mode 100644 index 0000000..686fc23 --- /dev/null +++ b/fn/-z4h-autosuggest-fetch @@ -0,0 +1,34 @@ +#!/usr/bin/env zsh + +if [[ -z $BUFFER || $CONTEXT != start ]]; then + unset POSTDISPLAY _z4h_autosuggest_buffer _z4h_autosuggestion +else + () { + if [[ "$BUFFER" == "${_z4h_autosuggest_buffer-}"* ]]; then + if (( ${#BUFFER} == ${#_z4h_autosuggest_buffer} )); then + return + elif [[ -v _z4h_autosuggestion ]]; then + if [[ -z "$_z4h_autosuggestion" ]]; then + return + elif [[ $POSTDISPLAY == ${BUFFER:${#_z4h_autosuggest_buffer}}* ]]; then + POSTDISPLAY="${POSTDISPLAY:$((${#BUFFER} - ${#_z4h_autosuggest_buffer}))}" + typeset -g _z4h_autosuggest_buffer="$BUFFER" + return + fi + fi + fi + local suggestion + if [[ ${+_ZSH_AUTOSUGGEST_DISABLED} == 0 && + ${#BUFFER} -le ${ZSH_AUTOSUGGEST_BUFFER_MAX_SIZE:-${#BUFFER}} ]]; then + _zsh_autosuggest_fetch_suggestion "$BUFFER" + fi + POSTDISPLAY=${suggestion:${#BUFFER}} + typeset -g _z4h_autosuggest_buffer="$BUFFER" + typeset -g _z4h_autosuggestion="$suggestion" + } +fi + +if [[ -n $POSTDISPLAY ]]; then + region_highlight+=( + "${#BUFFER} $((${#BUFFER} + ${#POSTDISPLAY})) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE") +fi diff --git a/fn/-z4h-cd-rotate b/fn/-z4h-cd-rotate new file mode 100644 index 0000000..70ebaf6 --- /dev/null +++ b/fn/-z4h-cd-rotate @@ -0,0 +1,11 @@ +#!/usr/bin/env zsh + +(( ${+_z4h_dir_hist_fd} )) && -z4h-update-dir-history + +() { + eval "$_z4h_opt" + while (( $#dirstack )) && ! pushd -q $1 &>/dev/null; do + popd -q $1 + done + (( $#dirstack )) +} "$@" && -z4h-redraw-prompt diff --git a/fn/-z4h-check-rc-zwcs b/fn/-z4h-check-rc-zwcs new file mode 100644 index 0000000..01186c6 --- /dev/null +++ b/fn/-z4h-check-rc-zwcs @@ -0,0 +1,25 @@ +#!/usr/bin/env zsh + +zstyle -T :z4h: check-orphan-rc-zwc || return 0 + +local -a zwcs=($@) +local -a rcs=(${^@:r}(N)) + +if (( $#rcs != $#zwcs )); then + local home=~ + local zdotdir=${${${(q)ZDOTDIR}/#${(q)home}/'~'}//\%/%%} + print -Pru2 -- "%F{3}z4h%f: detected %F{1}orphan zwc files%f" + print -Pru2 -- "" + local rc rm_args=() + for rc in ${${(@)zwcs:r}:|rcs}; do + rm_args+=("%U$zdotdir/${rc:t}.zwc%u") + print -Pru2 -- " $rm_args[-1]" + done + print -Pru2 -- "" + print -Pru2 -- "It is highly recommended to delete them:" + print -Pru2 -- "" + print -Pru2 -- " %F{2}rm%f -f -- ${(j: :)rm_args}" + print -Pru2 -- "" +fi + +return 0 diff --git a/fn/-z4h-chsh b/fn/-z4h-chsh new file mode 100644 index 0000000..2d5b364 --- /dev/null +++ b/fn/-z4h-chsh @@ -0,0 +1,123 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +local user=${(%):-%n} +[[ -n $user && -r /etc/shells && $_z4h_exe == /* && -x $_z4h_exe && + $user != cloudshell-user && -t 0 && -t 1 && -t 2 ]] || return 0 + +if [[ $OSTYPE == linux* ]]; then + [[ -r /etc/passwd ]] || return 0 + + if (( ! $+commands[chsh] )); then + [[ $+commands[sudo] == 1 || -w /etc ]] || return 0 + fi + + local passwd + passwd="$($Z4H/stickycache/no-chsh || return '_z4h_err()' + local home=~ + local zdotdir=${${${(q)ZDOTDIR}/#${(q)home}/'~'}//\%/%%} + print -Pr -- "Won't ask again unless %U\$Z4H/stickycache/no-chsh%u is deleted." + return 1 + fi + [[ $choice == $'\n' ]] && continue + if [[ $choice != (y|Y) ]]; then + print -Pr -- '%F{3}z4h%f: invalid choice: '${(q-)choice//\%/%%} + continue + fi + error=1 + print + if [[ -z $my_shell ]]; then + print -Pr -- "Adding %F{2}${_z4h_exe//\%/%%}%f to %U/etc/shells%u." + local precmd=(command) + [[ -w /etc/shells ]] || precmd+=(sudo) + $precmd tee -a /etc/shells >/dev/null <<<$_z4h_exe || continue + my_shell=$_z4h_exe + (( $#precmd == 1 )) || print + fi + print -Pr -- "Changing login shell to %F{2}${my_shell//\%/%%}%f." + if (( $+commands[chsh] )); then + command chsh -s $my_shell || continue + else + local MATCH MBEGIN MEND + local p=("${(@)${(@f)passwd}/#%(#m)$user:*/${MATCH%:*}:$my_shell}") + if [[ ${(F)p} == $passwd ]]; then + print -Pru2 -- '%F{3}z4h%f: %F{1}internal error%f' + return '_z4h_err()' + fi + local precmd=(command) + [[ -w /etc ]] || precmd+=(sudo) + $precmd tee -- /etc/passwd.bak.$$ >/dev/null <<<${(F)p} || continue + $precmd mv -f -- /etc/passwd.bak.$$ /etc/passwd || continue + fi + print -Pr -- "Changed login shell of %F{3}${user//\%/%%}%f to %F{2}${my_shell//\%/%%}%f." + print + return 0 + done + ) +) && export SHELL=${my_shell:-$_z4h_exe} diff --git a/fn/-z4h-cmd-bindkey b/fn/-z4h-cmd-bindkey new file mode 100644 index 0000000..aec8fde --- /dev/null +++ b/fn/-z4h-cmd-bindkey @@ -0,0 +1,54 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +zparseopts -D -F -- || return '_z4h_err()' + +if (( ARGC < 2 )); then + -z4h-help-bindkey >&2 + return 1 +fi + +(( ${+_z4h_key} )) || return 0 + +local kb seq seqs new_seqs + +for kb in ${@:2}; do + seqs=('') + for kb in ${(@s: :)kb}; do + if [[ -n ${seq::=$_z4h_key[$kb]} ]]; then + seqs=(${^seqs}$seq) + else + case $kb in + ?~[a-z]) + seqs=(${^seqs}$kb ${^seqs}${(L)kb}) + ;; + Ctrl+[A-Z'[]\']) + seqs=(${^seqs}'^'$kb[-1]) + ;; + (Alt|Option)+[A-Z'[]\/.,']) + new_seqs=(${^seqs}'^['$kb[-1] ${^seqs}'^['${(L)kb[-1]}) + if zstyle -T :z4h:bindkey macos-option-as-alt && + [[ -n ${seq::=$_z4h_macos_opt_key[$kb[-1]]} ]]; then + new_seqs+=(${^seqs}$seq) + if [[ -n ${seq::=$_z4h_macos_opt_key[${(L)kb[-1]}]} ]]; then + new_seqs+=(${^seqs}$seq) + fi + fi + seqs=($new_seqs) + ;; + Ctrl+(Alt|Option)+[A-Z'[]\']) + seqs=(${^seqs}'^[^'$kb[-1]) + ;; + *) + print -Pru2 -- '%F{3}z4h%f: invalid key binding: %F{1}'${kb//\%/%%}'%f' + return '_z4h_err()' + ;; + esac + fi + done +done + +for seq in ${(u)seqs}; do + builtin bindkey -- $seq $1 +done diff --git a/fn/-z4h-cmd-compile b/fn/-z4h-cmd-compile new file mode 100644 index 0000000..849c782 --- /dev/null +++ b/fn/-z4h-cmd-compile @@ -0,0 +1,9 @@ +#!/usr/bin/env zsh + +local file +zparseopts -D -F -- || return '_z4h_err()' +emulate zsh -o extended_glob -c 'local files=(${^@}(N))' +builtin set -- +for file in "${files[@]}"; do + -z4h-compile "$file" +done diff --git a/fn/-z4h-cmd-docker b/fn/-z4h-cmd-docker new file mode 100644 index 0000000..7b235fd --- /dev/null +++ b/fn/-z4h-cmd-docker @@ -0,0 +1,7 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +local term +zstyle -s :z4h:docker term term || term=${TERM:/tmux-256color/screen-256color} +TERM=${term:-$TERM} command docker "$@" diff --git a/fn/-z4h-cmd-help b/fn/-z4h-cmd-help new file mode 100644 index 0000000..7a36e7c --- /dev/null +++ b/fn/-z4h-cmd-help @@ -0,0 +1,54 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +if (( ARGC == 1 )); then + case $1 in + install) + print -Pr -- "Usage: %F{2}z4h%f %Binstall%b [-f|--force] [%Uuser/project[@branch]%u]..." + print -Pr -- "" + print -Pr -- "Clone or update GitHub project(s)." + ;; + update) + print -Pr -- "Usage: %F{2}z4h%f %Bupdate%b" + print -Pr -- "" + print -Pr -- "Update %Bzsh4humans%b and all dependencies (%Bfzf%b, %Bzsh-autosuggestions%b, etc.)." + ;; + init) + print -Pr -- "Usage: %F{2}z4h%f %Binit%b" + print -Pr -- "" + print -Pr -- "Initialize Zsh. Should be called just once from %U.zshrc%u." + ;; + help) + print -Pr -- "Usage: %F{2}z4h%f %Bhelp%b [%Ucommand%u]" + print -Pr -- "" + print -Pr -- "Print help for the command." + ;; + *) + if (( $+functions[-z4h-help-$1] )); then + -z4h-help-$1 + else + print -Pru2 -- "%F{3}z4h%f: unknown command: %F{1}${1//\%/%%}%f" + return 1 + fi + ;; + esac + return +fi + +if (( ARGC == 0 )); then + local fd=1 ret=0 +else + local fd=2 ret=1 +fi + +print -Pru$fd -- "Usage: %F{2}z4h%f %Binstall%b [-f|--force] [%Uuser/project[@branch]%u]..." +print -Pru$fd -- " %Bupdate%b" +print -Pru$fd -- " %Binit%b" +print -Pru$fd -- " %Bsource%b [-c|--compile] [--] [%Ufile%u]..." +print -Pru$fd -- " %Bcompile%b [--] [%Ufile%u]..." +print -Pru$fd -- " %Bload%b [-c|--compile] [--] [%Udir%u]..." +print -Pru$fd -- " %Bbindkey%b [--] zle-widget [key-binding] [key-binding]..." +print -Pru$fd -- " %Bssh%b [%Ussh-options%u] [%Uuser@%u]%Uhostname%u" +print -Pru$fd -- " %Bhelp%b [%Ucommand%u]" +return ret diff --git a/fn/-z4h-cmd-ssh b/fn/-z4h-cmd-ssh new file mode 100644 index 0000000..43f8375 --- /dev/null +++ b/fn/-z4h-cmd-ssh @@ -0,0 +1,519 @@ +#!/usr/bin/env zsh + +# TODO: write proper docs for these configuration options. +# +# zstyle ':z4h:ssh:my_host' enable 'yes' +# zstyle ':z4h:ssh:*' send-extra-files '~/foo' '"$ZDOTDIR"/bar' +# zstyle ':z4h:ssh:*' retrieve-extra-files '~/foo' '"$ZDOTDIR"/bar' +# zstyle ':z4h:ssh:*' ssh-command command ssh +# zstyle ':z4h:ssh:*' retrieve-history $ZDOTDIR/.zsh_history.remote +# zstyle ':z4h:ssh:*' term tmux-256color +# +# z4h-ssh-configure() { +# z4h_ssh_prelude+=( +# "export BLAH=${(q)BLAH}" +# ) +# z4h_ssh_send_files+=( +# ~/foo '~/foo' +# $ZDOTDIR/bar '"$ZDOTDIR"/bar' +# ) +# z4h_ssh_setup+=( +# 'echo "setting up"' +# ) +# z4h_ssh_run=( +# 'echo "starting z4h"' +# $z4h_ssh_launch_commands +# ) +# z4h_ssh_teardown+=( +# 'echo "tearing down"' +# ) +# z4h_ssh_retrieve_files+=( +# '~/foo' ~/foo +# '"$ZDOTDIR"/bar' $ZDOTDIR/bar +# ) +# } + +eval "$_z4h_opt" +-z4h-check-core-params || return + +if (( _z4h_dangerous_root )); then + print -Pru2 -- "%F{3}z4h%f: refusing to %Bssh%b as %F{1}root%f" + return 1 +fi + +local -i must_passthrough i +local -a pos +for ((i = 1; i <= $#; ++i)); do + case $*[i] in + --) (( ++i <= $# )) && pos+=({$i..$#}); break;; + -[OG]) must_passthrough=1; ((++i));; + -*) [[ bcDEeFIiJLlmOopQRSWw == *${${*[i]}[-1]}* ]] && ((++i));; + *) pos+=($i);; + esac +done + +local z4h_min_version +z4h_min_version=${$(<$Z4H/zsh4humans/version)%$'\r'} || return +if [[ $z4h_min_version != <1-> ]]; then + print -Pru2 -- '%F{3}z4h%f: invalid file content: %F{1}%U$Z4H/zsh4humans/version%f%u' + return 1 +fi +local -r z4h_min_version + +local -r z4h_ssh_client=${${(%):-%m}:-unknown} + +local z4h_ssh_host +if (( $#pos == 1 )); then + local user_host=$*[pos[1]] + z4h_ssh_host=${${user_host##*@}%%:*} +fi +local -r z4h_ssh_host + +[[ -n $z4h_ssh_host ]] || must_passthrough=1 + +local -i z4h_ssh_enable=$(( !must_passthrough )) +zstyle -t :z4h:ssh:$z4h_ssh_host enable || z4h_ssh_enable=0 + +local -i mkdir_control_master=0 +local default_ssh_command=(command ssh) +if (( z4h_ssh_enable )); then + mkdir_control_master=1 + default_ssh_command+=( + -o ControlMaster=auto + -o ControlPersist=5 + -o ControlPath='~/.ssh/s/%C') +fi + +local -a z4h_ssh_command +if ! zstyle -a :z4h:ssh:$z4h_ssh_host ssh-command z4h_ssh_command; then + z4h_ssh_command=($default_ssh_command) +fi + +local term +zstyle -s :z4h:ssh:$z4h_ssh_host term term || term=${TERM:/tmux-256color/screen-256color} + +local -A z4h_ssh_send_files z4h_ssh_retrieve_files +local -a z4h_ssh_prelude z4h_ssh_setup z4h_ssh_run z4h_ssh_teardown +local -aU z4h_retrieve_history + +if (( !must_passthrough )); then + z4h_ssh_prelude=( + '"export" ZDOTDIR="$HOME"' + 'if command -v "locale" >"/dev/null" 2>&1; then + "export" LC_ALL="C" + fi') + + z4h_ssh_send_files=( + $ZDOTDIR/.zshenv '"$ZDOTDIR"/.zshenv' + $ZDOTDIR/.zprofile '"$ZDOTDIR"/.zprofile' + $ZDOTDIR/.zshrc '"$ZDOTDIR"/.zshrc' + $ZDOTDIR/.zlogin '"$ZDOTDIR"/.zlogin' + $ZDOTDIR/.zlogout '"$ZDOTDIR"/.zlogout') + + local file + for file in $ZDOTDIR/.p10k{,-ascii}{,-8color}.zsh(N) $ZDOTDIR/.zsh_history.*:$z4h_ssh_host(N); do + z4h_ssh_send_files[$file]='"$ZDOTDIR"/'${(q)file:t} + done + + local -a extra_files + if zstyle -a :z4h:ssh:$z4h_ssh_host send-extra-files extra_files; then + local src dst + for dst in $extra_files; do + eval "src=$dst" + z4h_ssh_send_files[$src]=$dst + done + fi + + z4h_ssh_run=( + 'if "[" "-f" "$ZDOTDIR"/.zshenv "-a" "-r" "$ZDOTDIR"/.zshenv "]"; then + "." "$ZDOTDIR"/.zshenv + else + >&2 "printf" "\\033[33mz4h\\033[0m: not a readable file: \\033[31m%s\033[0m\n" "$ZDOTDIR"/.zshenv + "false" + fi') + + if zstyle -a :z4h:ssh:$z4h_ssh_host retrieve-extra-files extra_files; then + local src dst + for src in $extra_files; do + eval "dst=$src" + z4h_ssh_retrieve_files[$src]=$dst + done + fi + + zstyle -a :z4h:ssh:$z4h_ssh_host retrieve-history z4h_retrieve_history || z4h_retrieve_history=() +fi + +local configure +if zstyle -s :z4h:ssh:$z4h_ssh_host configure configure; then + eval $configure || return +elif (( $+functions[z4h-ssh-configure] )); then + z4h-ssh-configure || return +fi + +if (( ! $#z4h_ssh_command )); then + print -Pru2 -- '%F{3}z4h%f: empty %F{1}z4h_ssh_command%f' + return 1 +fi + +if [[ $mkdir_control_master == 1 && + ${(pj:\0:)z4h_ssh_command} == ${(pj:\0:)default_ssh_command} && + ! -d ~/.ssh/s && -n ~(#qNU) ]]; then + zf_mkdir -pm 700 ~/.ssh/s || return + { + >~/.ssh/s/README <<\END +This directory has been created by `z4h ssh`. It stores control sockets +for SSH connections. See ControlMaster, ControlPath and ControlPersist +in `man ssh_config`. This directory must not be writable by anyone other +than the current user. +END + } || return + if [[ -e ~/.ssh/control-master/README ]]; then + { + >>~/.ssh/s/README <<\END + +You might also have ~/.ssh/control-master with the same file in it. This +is the old directory that was used by zsh4humans for the same purpose +before 2021-11-14. If you don't reference that directory explicitly from +~/.ssh/config, you can safely delete it. +END + } || return + fi +fi + +if (( must_passthrough || !z4h_ssh_enable )); then + TERM=${term:-$TERM} "${z4h_ssh_command[@]}" "$@" + return +fi + +if (( $#z4h_retrieve_history )); then + local local_hist_tmp=$Z4H/tmp/ssh-history.tmp.$sysparams[pid] + z4h_ssh_retrieve_files[\$HISTFILE]=$local_hist_tmp + zf_rm -f -- $local_hist_tmp || return +else + local local_hist_tmp= +fi + +local file +for file in "${(@kv)z4h_ssh_send_files}"; do + if [[ -z $file ]]; then + print -Pru2 -- '%F{3}z4h%f: empty element(s) in %F{1}z4h_ssh_send_files%f' + return 1 + fi + if [[ $file == */ ]]; then + print -Pru2 -- "%F{3}z4h%f: element(s) of %Bz4h_ssh_send_files%b end with %B/%b: %F{1}${file//\%/%%}%f" + return 1 + fi +done +for file in "${(@kv)z4h_ssh_retrieve_files}"; do + if [[ -z $file ]]; then + print -Pru2 -- '%F{3}z4h%f: empty element(s) in %F{1}z4h_ssh_retrieve_files%f' + return 1 + fi + if [[ $file == */ ]]; then + print -Pru2 -- "%F{3}z4h%f: element(s) of %Bz4h_ssh_retrieve_files%b end with %B/%b: %F{1}${file//\%/%%}%f" + return 1 + fi +done +for file in "${(@)z4h_retrieve_history}"; do + if [[ -z $file ]]; then + print -Pru2 -- '%F{3}z4h%f: empty element(s) in %F{1}z4h_retrieve_history%f' + return 1 + fi + if [[ $file == */ ]]; then + print -Pru2 -- "%F{3}z4h%f: element(s) of %Bz4h_retrieve_history%b end with %B/%b: %F{1}${file//\%/%%}%f" + return 1 + fi + if [[ -e $file ]]; then + if [[ ! ( -f $file && -r $file && -w $file ) ]]; then + print -Pru2 -- "%F{3}z4h%f: element of %Bz4h_retrieve_history%b is not a readable & writable file: %F{1}${file//\%/%%}%f" + return 1 + fi + elif [[ -d ${file:h} ]]; then + if [[ ! -w ${file:h} ]]; then + print -Pru2 -- "%F{3}z4h%f: element of %Bz4h_retrieve_history%b is in a non-writable directory: %F{1}${file//\%/%%}%f" + return 1 + fi + else + zf_mkdir -p -- ${file:h} || return + fi +done + +if (( $#z4h_ssh_retrieve_files && ! $+commands[base64] )); then + print -Pru2 -- '%F{3}z4h%f: command not found: %F{1}base64%f' + return 1 +fi + +local tmpdir +if (( $+commands[mktemp] )); then + tmpdir=$(command mktemp -d -- $Z4H/tmp/ssh.XXXXXXXXXX) || return +else + tmpdir=$Z4H/tmp/ssh.tmp.$sysparams[pid] + zf_rm -rf -- $tmpdir || return + zf_mkdir -- $tmpdir || return +fi + +{ + local -i i=0 + local src dst + local indices=() send_to=() + for src dst in ${(kv)z4h_ssh_send_files}; do + (( ++i )) + send_to+=($dst) + [[ -e $src ]] || continue + local target=${src:A} + if [[ -z $target(#qN.) && -z $target(#qN/) ]]; then + print -Pru2 -- "%F{3}z4h%f: unsupported file type: %F{1}${src//\%/%%}%f" + return 1 + fi + if [[ ${tmpdir:A} == $target(|/*) ]]; then + print -Pru2 -- "%F{3}z4h%f: cannot send file: %F{1}${src//\%/%%}%f" + return 1 + fi + zf_ln -s -- $target $tmpdir/$i || return + indices+=($i) + done + + local -a retrieve_from retrieve_to + local from to + for from to in ${(kv)z4h_ssh_retrieve_files}; do + retrieve_from+=($from) + retrieve_to+=($to) + done + + local dump_marker=${(%):-%n}.$sysparams[pid].$EPOCHSECONDS.$RANDOM + + local script + script=${"$(<$Z4H/zsh4humans/sc/ssh-bootstrap)"//$'\r'} || return + script=${script//'^TERM^'/${(q)term}} + script=${script//'^MIN_VERSION^'/${(q)z4h_min_version}} + script=${script//'^SSH_HOST^'/${(q)z4h_ssh_host}} + script=${script//'^SSH_CLIENT^'/${(q)z4h_ssh_client}} + script=${script//'^SSH_ARGS^'/${(q)${(j: :)@}}} + script=${script//'^PRELUDE^'/${(F)z4h_ssh_prelude}} + script=${script//'^SEND_TO^'/${(j: :)send_to}} + script=${script//'^SETUP^'/${(F)z4h_ssh_setup}} + script=${script//'^RUN^'/${(F)z4h_ssh_run}} + script=${script//'^TEARDOWN^'/${(F)z4h_ssh_teardown}} + if (( $#retrieve_from )); then + script=${script//'^EMPTY_RETRIEVE_FROM^'/"'false'"} + else + script=${script//'^EMPTY_RETRIEVE_FROM^'/"'true'"} + fi + script=${script//'^RETRIEVE_FROM^'/${(j: :)retrieve_from}} + script=${script//'^DUMP_MARKER^'/${(q)dump_marker}} + script=${script//'^CAN_SAVE_RESTORE_SCREEN^'/${_z4h_can_save_restore_screen}} + + script=${script//'^DUMP_POS^'/${(r:8:: :)${#script}}} + + print -r -- $script >$tmpdir/script || return + + local tar_v tar_c_opt tar_x_opt + if tar_v=$(command tar --version 2>/dev/null) && [[ $tar_v == *'GNU tar'* ]]; then + tar_c_opt=(--owner=0 --group=0) + tar_x_opt=(--warning=no-unknown-keyword --warning=no-timestamp --no-same-owner) + fi + + if (( $#indices )); then + command tar -C $tmpdir $tar_c_opt -czhf - -- $indices >>$tmpdir/script || return + fi + + local args=("$@") + args[pos[1],pos[1]-1]=('-T') + local remote_script=.z4h-ssh.${(%):-%n}.$sysparams[pid].$EPOCHSECONDS.$RANDOM + + # Tricky corner cases where this command must work: + # + # 1. The remote shell is csh (default on FreeBSD). + # 2. There is no /tmp on the remote host (e.g., Termux). + # 3. TMPDIR is not set. + # 4. TMPDIR has spaces in it. + # + # The next command (the one that invokes /bin/sh) must also work in these + # cases. It should also propagate the exit status of /bin/sh. + local cmd="test -w /tmp && cat >/tmp/$remote_script && echo 1 && exit" + cmd+=" || " + cmd+="test ! -e /tmp/$remote_script && cat >~/$remote_script && echo 2" + local loc + loc=$("${z4h_ssh_command[@]}" "${args[@]}" $cmd <$tmpdir/script) || return +} always { + zf_rm -rf -- $tmpdir +} + +case ${loc//[[:space:]]} in + 1) remote_script="/tmp/$remote_script";; + 2) remote_script="~/$remote_script";; + *) + print -Pru2 -- "%F{3}z4h%f: failed to upload bootstrap script" + return 1 + ;; +esac + +args[pos[1]]='-t' + +local stty +if [[ -v commands[stty] && -v _z4h_tty_fd ]]; then + stty=$(command stty -g <&$_z4h_tty_fd 2>/dev/null) || stty= +fi + +{ ( # subshell to avoid TTOU + +local -i bypass=0 + +local -i pid=$sysparams[pid] + +{ + setopt no_multi_os + "${z4h_ssh_command[@]}" "${args[@]}" "sh $remote_script" 2>&1 1>&3 | + LC_ALL=C command grep -vxE '(Shared c|C)onnection to .* closed\.(.)?' >&2 + return $pipestatus[1] +} 3>&1 | { + if (( $+commands[mktemp] )); then + tmpdir=$(command mktemp -d -- $Z4H/tmp/ssh.XXXXXXXXXX) || return + else + tmpdir=$Z4H/tmp/ssh.tmp.$sysparams[pid] + zf_rm -rf -- $tmpdir || return + zf_mkdir -- $tmpdir || return + fi + + unsetopt multibyte + local LC_ALL=C + + unset _z4h_saved_screen + + { + local buf= + local mark=$'\001z4h.'$dump_marker + while true; do + [[ -n $buf ]] || sysread 'buf[$#buf+1]' || return $(( $? != 5 )) + if [[ $buf != *$mark[1]* ]]; then + print -rn -- $buf + buf= + continue + fi + while true; do + print -rn -- ${buf%%$mark[1]*} + buf=$mark[1]${buf#*$mark[1]} + local -i prefix=$(($#buf < $#mark ? $#buf : $#mark)) + (( prefix )) || continue + [[ ${buf:0:$prefix} == ${mark:0:$prefix} ]] && break + print -rn -- $buf[1] + buf[1]="" + continue 2 + done + while (( $#buf < $#mark )) && [[ $mark == $buf* ]]; do + # What should we do if the output ends with a proper prefix of mark? + # Print it or not? Return an error or not? We choose to not print and return + # success iff we've reached eof. + sysread -s $(($#mark - $#buf)) 'buf[$#buf+1]' && continue + return $(( $? != 5 )) + done + if [[ $buf != $mark* ]]; then + print -rn -- $buf[1] + buf[1]="" + continue + fi + buf[1,$#mark]="" + while (( $#buf < 16 )); do + sysread -s $((16 - $#buf)) 'buf[$#buf+1]' && continue + return $(( $? != 5 )) + done + + { + case ${buf[1,16]%% #} in + bypass) + bypass=1 + break + ;; + save-screen) + (( _z4h_can_save_restore_screen )) || return + local _z4h_saved_screen= + -z4h-save-screen || return + _z4h_saved_screen+=x + continue + ;; + restore-screen) + [[ -n $_z4h_saved_screen ]] || return + _z4h_saved_screen[-1]= + -z4h-restore-screen || return + unset _z4h_saved_screen + continue + ;; + <->) + local -i len=buf[1,16] + ;; + *) + return 1 + ;; + esac + } always { + buf=${buf:16} + } + + (( len )) || continue + if [[ -d $tmpdir ]]; then + local dump_file=$tmpdir/dump.base64 + else + local dump_file=/dev/null + fi + { + local -i n=$((len < $#buf ? len : $#buf)) + print -rn -- $buf[1,n] || return + (( len -= n )) + buf[1,n]="" + while (( len )); do + sysread -s $((len > 65636 ? 65636 : len)) -o 1 -c n || return $(( $? != 5 )) + (( len -= n, 1 )) + done + } >$dump_file || return + if [[ $dump_file != /dev/null ]]; then + if base64 -d <<<'Cg==' &>/dev/null; then + local base64_opt=-d + else + local base64_opt=-D + fi + <$tmpdir/dump.base64 command base64 $base64_opt | + command tar -C $tmpdir $tar_x_opt -xzf - || return + local -i i + for i in {1..$#retrieve_to}; do + local src=$tmpdir/$i + local dst=$retrieve_to[i] + [[ -e $src ]] || continue + if [[ -e $dst ]]; then + zf_rm -rf -- $dst || return + fi + if ! command mv -f -- $src $dst 2>/dev/null; then + command cp -rf -- $src $dst || return + fi + done + if [[ -s $local_hist_tmp ]]; then + local local_hist + for local_hist in $z4h_retrieve_history; do + local TMPPREFIX=$local_hist + () { + () { fc -pa -- $1 $HISTSIZE $SAVEHIST } $1 && zf_mv -f -- $1 $local_hist + } =(command cat -- $local_hist(N) $local_hist_tmp) || return + done + fi + fi + done + } always { + local -i err=$? + zf_rm -rf -- $tmpdir $local_hist_tmp + if (( err )); then + kill -- -$pid 2>/dev/null + fi + } +} + +if (( bypass )); then + setopt no_multi_os + { + TERM=${term:-$TERM} "${z4h_ssh_command[@]}" "${args[@]}" 2>&1 1>&3 | + LC_ALL=C command grep -vxE '(Shared c|C)onnection to .* closed\.(.)?' >&2 + return $pipestatus[1] + } 3>&1 +fi + + ) } always { + [[ -n $stty ]] && command stty $stty <&$_z4h_tty_fd 2>/dev/null +} diff --git a/fn/-z4h-cmd-sudo b/fn/-z4h-cmd-sudo new file mode 100644 index 0000000..c1aedf6 --- /dev/null +++ b/fn/-z4h-cmd-sudo @@ -0,0 +1,7 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +local term +zstyle -s :z4h:sudo term term || term=${TERM:/tmux-256color/screen-256color} +TERM=${term:-$TERM} command sudo "$@" diff --git a/fn/-z4h-cmd-tty-wait b/fn/-z4h-cmd-tty-wait new file mode 100644 index 0000000..4aafa13 --- /dev/null +++ b/fn/-z4h-cmd-tty-wait @@ -0,0 +1,32 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +local -a timeout pattern +zparseopts -D -F -- \ + {t,-timeout-seconds}:=timeout \ + {p,-lines-columns-pattern}:=pattern \ + || return '_z4h_err()' + +if (( $#pattern == 2 )); then + pattern=$pattern[2] +else + return '_z4h_err()' +fi + +if (( $#timeout == 2 )); then + [[ $timeout[2] == <->(|.<->) ]] || return '_z4h_err()' + typeset -F timeout=$timeout[2] +else + return '_z4h_err()' +fi + +[[ $TERM == (screen|tmux)* ]] && return 0 +[[ -v commands[true] ]] || return 0 + +local -F deadline='EPOCHREALTIME + timeout' +while [[ "$LINES $COLUMNS" != $~pattern ]] && (( EPOCHREALTIME < deadline )); do + command true +done + +return 0 diff --git a/fn/-z4h-cmd-update b/fn/-z4h-cmd-update new file mode 100644 index 0000000..402a9b8 --- /dev/null +++ b/fn/-z4h-cmd-update @@ -0,0 +1,82 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" +-z4h-check-core-params || return + +if (( ARGC )); then + print -Pru2 -- '%F{3}z4h%f: unexpected %F{1}update%f argument' + return '_z4h_err()' +fi + +if (( _z4h_dangerous_root )); then + print -Pru2 -- "%F{3}z4h%f: refusing to %Bupdate%b as %F{1}root%f" + return 1 +fi + +local old=$Z4H.old.$$ +local new=$Z4H.new.$$ + +{ + zf_rm -rf -- $old $new || return + zf_mkdir -p -- $new || return + print -n >$new/.updating || return + + Z4H_UPDATING=$Z4H Z4H=$new /dev/null $_z4h_exe -ic ' + (( $? )) && "exit" "1" + "builtin" "emulate" "zsh" "-o" "no_aliases" + [[ $Z4H == '${(q)new}' ]] || exit 0 + print -n >$Z4H/tmp/update-successful' || return + + if [[ ! -e $new/tmp/update-successful ]]; then + local home=~ + local zdotdir=${${${(q)ZDOTDIR}/#${(q)home}/'~'}//\%/%%} + local z4h=${${${(q)Z4H}/#${(q)home}/'~'}//\%/%%} + print -Pru2 -- '%F{3}z4h%f: %B$Z4H%b %F{1}does not propagate%f through %U.zshrc%u' + print -Pru2 -- '' + print -Pru2 -- 'Change %U'$zdotdir'/.zshrc%u to keep %BZ4H%b intact if already set.' + print -Pru2 -- '' + print -Pru2 -- 'For example:' + print -Pru2 -- '' + print -Pru2 -- ' %F{2}:%f %F{3}"${Z4H:=${XDG_CACHE_HOME:-$HOME/.cache}/zsh4humans}"%f' + print -Pru2 -- '' + print -Pru2 -- 'Note: The leading colon (%F{2}:%f) is necessary.' + return 1 + fi + + zf_rm -- $new/tmp/update-successful || return + zf_rm -- $new/.updating || return + -z4h-mv $Z4H $old 2>/dev/null || zf_rm -rf -- $Z4H || return + if [[ -e $Z4H ]]; then + local home=~ + local z4h=${${${(q)Z4H}/#${(q)home}/'~'}//\%/%%} + print -Pru2 -- '%F{3}z4h%f: %F{1}cannot delete %U'$z4h'%u%f' + print -Pru2 -- '' + print -Pru2 -- 'This might help:' + print -Pru2 -- '' + print -Pru2 -- ' %F{2}%Ucommand%u %Usudo%u rm%f -rf -- %U'$z4h'%u && %U%F{2}exec%u zsh%f' + print -Pru2 -- '' + print -Pru2 -- '%F{3}z4h%f: attempting to recover' + if [[ ! -e $Z4H/stickycache ]]; then + command cp -r -- $new/stickycache $Z4H/stickycache || true + fi + if [[ -o zle ]]; then + exec -- $_z4h_exe -i || return + else + exec -- $_z4h_exe -i --no-zle || return + fi + fi + -z4h-mv $new $Z4H || return +} always { + if (( $? )); then + -z4h-error-command update + fi + zf_rm -rf -- $old $new || return +} + +print -Pru2 -- "%F{3}z4h%f: %Bupdate successful%b" +print -Pru2 -- "%F{3}z4h%f: restarting %F{2}zsh%f" +if [[ -o zle ]]; then + exec -- $_z4h_exe -i || return +else + exec -- $_z4h_exe -i --no-zle || return +fi diff --git a/fn/-z4h-command-not-found b/fn/-z4h-command-not-found new file mode 100644 index 0000000..53e9900 --- /dev/null +++ b/fn/-z4h-command-not-found @@ -0,0 +1,36 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +local fd fname +if zstyle -s :z4h:command-not-found to-file fname && + [[ -w $fname || ! -e $fname && -w ${fname:h} ]]; then + fd=3 +else + fd=2 + fname=/dev/null +fi +{ + if (( $#functrace >= 2 )); then + print -r -- "$functrace[1]: command not found: $1" + return 127 + fi + local msg + if [[ -x /usr/lib/command-not-found ]]; then + msg="$(/usr/lib/command-not-found --no-failure-msg -- $1 2>&1)" + if [[ -n $msg ]]; then + print -r -- ${msg#$'\n'} + return 127 + fi + fi + if [[ -v commands[brew] && + -n $HOMEBREW_REPOSITORY && + -e $HOMEBREW_REPOSITORY/Library/Taps/homebrew/homebrew-command-not-found/cmd/which-formula.rb ]]; then + if msg="$(command brew which-formula --explain $1 2>/dev/null)" && [[ -n $msg ]]; then + print -r -- $msg + return 127 + fi + fi + print -r -- "zsh: command not found: $1" + return 127 +} 3>$fname >&$fd diff --git a/fn/-z4h-comp-files b/fn/-z4h-comp-files new file mode 100644 index 0000000..1cee5c6 --- /dev/null +++ b/fn/-z4h-comp-files @@ -0,0 +1,148 @@ +#!/usr/bin/env zsh + +() { + eval "$_z4h_opt" + + local -i dot_glob list_types + [[ $1 == on ]] && dot_glob=1 + [[ $2 == on ]] && list_types=1 + + if (( _z4h_only_dirs )); then + local dirs=($_z4h_path_prefix${^${(Q)_z4h_words}}/*(D-/Y1N:h:t)) + else + local dirs=($_z4h_path_prefix${^${(Q)_z4h_words}}/*(DY1N:h:t)) + fi + + -z4h-set-list-colors "$_z4h_curcontext" "$list_types" + local -i list_colors=$((!$?)) + + autoload +X -Uz -- -z4h-present-files -z4h-cursor-show -z4h-find -z4h-fzf + + local -i pct=60 + (( _z4h_can_save_restore_screen )) && pct=100 + + local -i recurse + () { + emulate -L zsh + # Set curcontext and dot_glob in case the value of recurse depends on them (via `zstyle -e`). + # Ideally we should run this with user options. + (( dot_glob )) && setopt dot_glob + local curcontext=$_z4h_curcontext + zstyle -T :z4h:${WIDGET#z4h-} recurse-dirs && recurse=1 + } + + local -i height=$(( ! (recurse && $#dirs) && 100 * ($#_z4h_words + 4) < pct * LINES ? $#_z4h_words + 4 : pct * LINES / 100 )) + + (( height >= 6 )) || (( height = 6 )) + (( height <= LINES - 1 )) || (( height = LINES - 1 )) + + local opts=( + --query=${_z4h_word_prefix:+"^$_z4h_word_prefix"} + --color=hl:201,hl+:201 + --with-nth=2 + --delimiter='\000' + --ansi + --exact + --no-mouse + --tiebreak=length,begin,index + --multi + --cycle + --border=horizontal + ) + + () { + emulate -L zsh + # Set dot_glob in case the value of find-flags depends on it (via `zstyle -e`). + # Ideally we should run this with user options. + (( dot_glob )) && setopt dot_glob + local -a bin + zstyle -a :z4h:${WIDGET#z4h-} find-command bin + if (( ! $#bin )) && (( $+commands[bfs] )); then + opts+=(--no-sort) + fi + } + + local cursor_y cursor_x + -z4h-get-cursor-pos || return + + if (( _z4h_can_save_restore_screen )); then + opts+=(--no-clear) + if { (( height <= cursor_y - 1 )) && zstyle -T :z4h: prompt-at-bottom } || + (( cursor_y - 1 > LINES - cursor_y && cursor_y - 1 > 6 )) && + { (( height > LINES - cursor_y )) || zstyle -T :z4h: prompt-at-bottom }; then + (( height <= cursor_y - 1 )) || (( height = cursor_y - 1 )) + local move=$'\e[0m\e['$((cursor_y-height))';1H' + opts+=(--layout=default) + elif (( LINES - cursor_y > 6 )); then + (( height <= LINES - cursor_y )) || (( height = LINES - cursor_y )) + local move=$'\e[0m\n\r' + opts+=(--layout=reverse) + else + local -i extra=$((height - LINES + cursor_y)) + print -rnu $_z4h_tty_fd -- ${(pl:$height::\n:)} || return + (( cursor_y += LINES - cursor_y - height )) + local move=$'\e[0m\e['$((cursor_y+1))';1H' + opts+=(--layout=reverse) + fi + local _z4h_saved_screen + -z4h-save-screen || return + else + print -u $_z4h_tty_fd || return + local move= + opts+=(--layout=reverse) + fi + + opts+=(--height=$height) + + { + local choice + choice="$( + unsetopt pipe_fail + exec 2>/dev/null + [[ -n $_z4h_path_prefix ]] && builtin cd -q -- $_z4h_path_prefix + { + print -r -- $sysparams[pid] + print -lr -- ${(Q)_z4h_words} + if (( recurse )); then + # Set curcontext to support this: + # + # zstyle -e :z4h:fzf-complete find-flags my-find-flags + # + # function my-find-flags() { + # if [[ $curcontext == :complete:cd:* ]]; then + # reply=(...) + # fi + # } + local curcontext=$_z4h_curcontext + -z4h-find $dot_glob $_z4h_only_dirs $dirs | command sed -n '/\/.*\// s/^..//p' + fi + } | { + local -a pids + IFS=' ' builtin read -rA pids || exit + print -r -- $pids $sysparams[pid] || exit + -z4h-present-files $list_colors $list_types 0 + } | { + local -a pids + IFS=' ' builtin read -rA pids || pids=() + print -rnu $_z4h_tty_fd -- $move + -z4h-cursor-show + 2>&$_z4h_tty_fd -z4h-fzf $opts + (( $#pids )) && builtin kill -- $pids + } + )" + } always { + -z4h-cursor-hide + if (( _z4h_can_save_restore_screen )); then + -z4h-restore-screen + print -rn -- $'\e[0m\e['$cursor_y';'$cursor_x'H' + else + builtin echoti cuu 1 + (( cursor_x > 1 )) && builtin echoti cuf $((cursor_x-1)) + fi + } + + [[ -n $choice ]] || return + choice=("${(@f)choice}") + typeset -g _z4h_reply=(0 ${${choice:1}%$'\0'*}) + [[ -z $choice[1] ]] || _z4h_reply[1]=1 +} "${options[dot_glob]}" "${options[list_types]}" diff --git a/fn/-z4h-comp-insert-all b/fn/-z4h-comp-insert-all new file mode 100644 index 0000000..d2da1ea --- /dev/null +++ b/fn/-z4h-comp-insert-all @@ -0,0 +1,44 @@ +#!/usr/bin/env zsh + +emulate -L zsh + +if (( $#_z4h_scaffolds == 1 )); then + local -a s=("${(@ps:\1:)_z4h_scaffolds}") + local PREFIX=$s[14] SUFFIX=$s[15] + $_z4h_orig_compadd \ + ${s[2]:+-P$s[2]} \ + ${s[3]:+-S$s[3]} \ + ${s[4]:+-p$s[4]} \ + ${s[5]:+-s$s[5]} \ + ${s[6]:+-i$s[6]} \ + ${s[7]:+-I$s[7]} \ + $s[8] \ + ${s[9]:+-r$s[9]} \ + ${s[10]:+-R$s[10]} \ + $s[11] \ + $s[12] \ + ${s[13]:+-W$s[13]} \ + -V- -2 -o nosort -J- -Q -U -a _z4h_words +else + local word scaffold + for word scaffold in "${(@)_z4h_words:^_z4h_scaffolds}"; do + local -a s=("${(@ps:\1:)scaffold}") + local PREFIX=$s[14] SUFFIX=$s[15] + $_z4h_orig_compadd \ + ${s[2]:+-P$s[2]} \ + ${s[3]:+-S$s[3]} \ + ${s[4]:+-p$s[4]} \ + ${s[5]:+-s$s[5]} \ + ${s[6]:+-i$s[6]} \ + ${s[7]:+-I$s[7]} \ + $s[8] \ + ${s[9]:+-r$s[9]} \ + ${s[10]:+-R$s[10]} \ + $s[11] \ + $s[12] \ + ${s[13]:+-W$s[13]} \ + -V- -2 -o nosort -J- -Q -U - $word + done +fi + +compstate[insert]=all diff --git a/fn/-z4h-comp-words b/fn/-z4h-comp-words new file mode 100644 index 0000000..f800207 --- /dev/null +++ b/fn/-z4h-comp-words @@ -0,0 +1,125 @@ +#!/usr/bin/env zsh + +() { + eval "$_z4h_opt" + + local -i list_types + [[ $1 == on ]] && list_types=1 + + typeset -ga _z4h_naturals + if (( $#_z4h_naturals < $#_z4h_words )); then + _z4h_naturals+=({$(($#_z4h_naturals+1))..$#_z4h_words}) + fi + + local -A seen + local -a indices + local word idx + for word idx in "${(@)_z4h_words:^_z4h_naturals}"; do + if (( ! ${+seen[$word]} )); then + seen[$word]=1 + indices+=($idx) + fi + done + + -z4h-set-list-colors "$_z4h_curcontext" "$list_types" + local -i list_colors=$((!$?)) + + zstyle -t :completion:${_z4h_curcontext}:default sort + local -i sort=$((!$?)) + + autoload +X -Uz -- -z4h-cursor-show -z4h-fzf + + local -i pct=60 + (( _z4h_can_save_restore_screen )) && pct=100 + + local -i height=$(( 100 * ($#indices + 4) < pct * LINES ? $#indices + 4 : pct * LINES / 100 )) + + (( height >= 6 )) || (( height = 6 )) + (( height <= LINES - 1 )) || (( height = LINES - 1 )) + + local opts=( + --query=${_z4h_word_prefix:+"^$_z4h_word_prefix"} + --color=hl:201,hl+:201 + --with-nth=2 + --delimiter='\000' + --ansi + --exact + --no-mouse + --tiebreak=length,begin,index + --multi + --cycle + --border=horizontal + ) + + local cursor_y cursor_x + -z4h-get-cursor-pos || return + + if (( _z4h_can_save_restore_screen )); then + opts+=(--no-clear) + if { (( height <= cursor_y - 1 )) && zstyle -T :z4h: prompt-at-bottom } || + (( cursor_y - 1 > LINES - cursor_y && cursor_y - 1 > 6 )) && + { (( height > LINES - cursor_y )) || zstyle -T :z4h: prompt-at-bottom }; then + (( height <= cursor_y - 1 )) || (( height = cursor_y - 1 )) + local move=$'\e[0m\e['$((cursor_y-height))';1H' + opts+=(--layout=default) + elif (( LINES - cursor_y > 6 )); then + (( height <= LINES - cursor_y )) || (( height = LINES - cursor_y )) + local move=$'\e[0m\n\r' + opts+=(--layout=reverse) + else + local -i extra=$((height - LINES + cursor_y)) + print -rnu $_z4h_tty_fd -- ${(pl:$height::\n:)} || return + (( cursor_y += LINES - cursor_y - height )) + local move=$'\e[0m\e['$((cursor_y+1))';1H' + opts+=(--layout=reverse) + fi + local _z4h_saved_screen + -z4h-save-screen || return + else + print >&$_z4h_tty_fd || return + local move= + opts+=(--layout=reverse) + fi + + opts+=(--height=$height) + + { + local choice + choice="$( + unsetopt pipe_fail + exec 2>/dev/null + { + # TODO: colorize files. + if (( sort )); then + local rows=() + for idx in $indices; do + rows+=($_z4h_descrs[idx]$'\0'$idx) + done + printf '%2$s\0%1$s\n' "${(@0)${(@o)rows}}" + else + for idx in $indices; do + printf '%s\0%s\n' $idx "$_z4h_descrs[idx]" + done + fi + } | { + print -rnu $_z4h_tty_fd -- $move + -z4h-cursor-show + 2>&$_z4h_tty_fd -z4h-fzf $opts + } + )" + } always { + -z4h-cursor-hide + if (( _z4h_can_save_restore_screen )); then + -z4h-restore-screen + print -rn -- $'\e[0m\e['$cursor_y';'$cursor_x'H' + else + builtin echoti cuu 1 + (( cursor_x > 1 )) && builtin echoti cuf $((cursor_x-1)) + fi + } + + [[ -n $choice ]] || return + choice=("${(@f)choice}") + typeset -g _z4h_reply=(0 ${${choice:1}%$'\0'*}) + [[ -z $choice[1] ]] || _z4h_reply[1]=1 +} "${options[list_types]}" diff --git a/fn/-z4h-compile b/fn/-z4h-compile new file mode 100644 index 0000000..64254f1 --- /dev/null +++ b/fn/-z4h-compile @@ -0,0 +1,41 @@ +#!/usr/bin/env zsh + +# Runs with user options. +# +# Precondition: [[ -e $1 ]]. + +local -a stat + +# Checking [[ -e "$1".zwc ]] is faster than redirecting stderr of zstat to /dev/null. +[[ -e "$1".zwc ]] && zstat +mtime -A stat -- "$1" "$1".zwc && { + # Negative indices to handle ksh_arrays. + (( stat[-1] == stat[-2] + 1 )) && return # common case + stat[-1]=() +} || { + zstat +mtime -A stat -- "$1" || return +} + +[[ -w "${1:h}" ]] || return + +local t +builtin strftime -s t '%Y%m%d%H%M.%S' $((stat + 1)) + +local tmp="$1".tmp."${sysparams[pid]}".zwc +{ + # This zf_rm is to work around bugs in NTFS and/or WSL. The following code fails there: + # + # touch a b + # chmod -w b + # zf_rm -f a b + # + # The last command produces this error: + # + # zf_mv: a: permission denied + (( !_z4h_dangerous_root )) && + zcompile -R -- "$tmp" "$1" && + command touch -ct $t -- "$tmp" && + zf_rm -f -- "$1".zwc && + zf_mv -f -- "$tmp" "$1".zwc +} always { + (( $? )) && zf_rm -f -- "$tmp" "$1".zwc 2>/dev/null +} diff --git a/fn/-z4h-compinit b/fn/-z4h-compinit new file mode 100644 index 0000000..9650bbe --- /dev/null +++ b/fn/-z4h-compinit @@ -0,0 +1,107 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +if [[ -v _z4h_compinit_fd ]]; then + zle -F $_z4h_compinit_fd + exec {_z4h_compinit_fd}>&- + unset _z4h_compinit_fd +fi + +unfunction compdef + +local -aU editors=( + vi vim nvim emacs nano gedit code kak kate mcedit joe $EDITOR $VISUAL + bat cat less more $PAGER) +zstyle ':completion:*:*:('${(j:|:)editors}'):*:*' ignored-patterns '*.zwc' + +zstyle ':completion:*' list-colors "${(@s.:.)LS_COLORS}" + +(( _z4h_use[zsh-completions] )) && + [[ -d $Z4H/zsh-completions/src ]] && + fpath+=($Z4H/zsh-completions/src) +(( _z4h_use[systemd] )) && + [[ -e $Z4H/systemd/shell-completion/zsh/_systemctl && -z ${^fpath}/_systemctl(#qN) ]] && + fpath+=($Z4H/systemd/shell-completion/zsh) + +if (( !_z4h_dangerous_root )) && zstyle -t ':completion::complete:' use-cache; then + local cache + zstyle -s ':completion::complete:' cache-path cache + : ${cache:=${ZDOTDIR:-~}/.zcompcache} + if [[ ! -e $cache ]]; then + zf_mkdir -m 0700 -p -- $cache + fi +fi + +local dump +zstyle -s ':z4h:compinit' dump-path dump +: ${dump:=$Z4H/cache/zcompdump-$EUID-$ZSH_VERSION} + +local -a stat files=(${^fpath}/^([^_]*|*~|*.zwc)(-.N)) +(( ! $#files )) || zstat -A stat +mtime -- $files +local real_sig=($ZSH_VERSION $ZSH_PATCHLEVEL $files $stat) +real_sig='# '${(V)${(pj:\0:)real_sig}}$'\n' + +local sig +if [[ -r $dump ]] && + sysread -s $#real_sig sig <$dump && + [[ $sig == $real_sig && -r $dump.zwc ]] && + zstat -A stat +mtime -- $dump $dump.zwc && + (( stat[2] == stat[1] + 1 )); then + -z4h-compinit-impl -C -d $dump +else + local tmp=$Z4H/tmp/zcompdump.$sysparams[pid] + zf_rm -f -- $dump $dump.zwc $tmp $tmp.2 + -z4h-compinit-impl -C -d $tmp + { print -rn -- $real_sig; <$tmp } >$tmp.2 + zf_rm -f -- $tmp + zf_mv -- $tmp.2 $dump + -z4h-compile $dump +fi + +# Replay compdef calls. +local args +for args in $_z4h_compdef; do + compdef "${(@0)args}" +done +unset _z4h_compdef + +local cmd +for cmd in helm kitty kubectl oc; do + # Homebrew ships broken completions for kubectl, so we use our own even + # if _comps[kubectl] is set. + # + # TODO: what about the rest of them in the list? Move them below? + [[ -v commands[$cmd] ]] && compdef -- -z4h-complete-$cmd $cmd +done + +for cmd in cargo bw gh rustup; do + if [[ -v commands[$cmd] && ! -v _comps[$cmd] ]]; then + compdef -- -z4h-complete-$cmd $cmd + fi +done + +for cmd in terraform vault packer; do + if [[ -v commands[$cmd] && ! -v _comps[$cmd] ]]; then + complete -o nospace -C =$cmd $cmd + fi +done + +if [[ -v commands[aws_completer] && ! -v _comps[aws] ]]; then + complete -C =aws_completer aws +fi + +if [[ -v commands[gcloud] && ! -v _comps[gcloud] ]]; then + local dirs=( + ${HOMEBREW_PREFIX:+$HOMEBREW_PREFIX/share/google-cloud-sdk} + ~/google-cloud-sdk + /usr/share/google-cloud-sdk + /snap/google-cloud-sdk/current + /snap/google-cloud-cli/current + /usr/lib/google-cloud-sdk + /usr/lib64/google-cloud-sdk + /opt/google-cloud-sdk + /opt/local/libexec/google-cloud-sdk + ) + source -- $^dirs/completion.zsh.inc(-.Ne'<[[ -r ${REPLY:a} ]]>') /dev/null +fi diff --git a/fn/-z4h-complete-bw b/fn/-z4h-complete-bw new file mode 100644 index 0000000..36a298b --- /dev/null +++ b/fn/-z4h-complete-bw @@ -0,0 +1,29 @@ +#!/usr/bin/env zsh + +local bw="${commands[bw]-}" +local comp="$Z4H"/cache/bw-completion-$EUID.zsh + +() { + [[ -n "$bw" ]] || return + + [[ "$comp" -nt "$bw" ]] || { + local tmp="$comp".tmp."${sysparams[pid]}" + "$bw" completion --shell zsh >"$tmp" || return + zf_mv -f -- "$tmp" "$comp" || return + -z4h-compile "$comp" || return + } + + () { + emulate -L zsh + local -h funcstack=(_bw) + builtin source -- "$comp" + } || true + + [[ -v functions[_bw] ]] || return + _comps[bw]=_bw + _bw "$@" +} "$@" || { + builtin unset '_comps[bw]' + _default "$@" + return +} diff --git a/fn/-z4h-complete-cargo b/fn/-z4h-complete-cargo new file mode 100644 index 0000000..cd6dacb --- /dev/null +++ b/fn/-z4h-complete-cargo @@ -0,0 +1,13 @@ +#!/usr/bin/env zsh + +(( $+commands[rustup] && $+commands[rustc] )) || { + unset '_comps[cargo]' + _default "$@" + return +} + +{ + builtin source <(rustup completions zsh cargo) +} always { + (( $+functions[_cargo] )) && _comps[cargo]=_cargo || unset '_comps[cargo]' +} diff --git a/fn/-z4h-complete-gh b/fn/-z4h-complete-gh new file mode 100644 index 0000000..def3859 --- /dev/null +++ b/fn/-z4h-complete-gh @@ -0,0 +1,28 @@ +#!/usr/bin/env zsh + +local gh="${commands[gh]-}" +local comp="$Z4H"/cache/gh-completion-$EUID.zsh + +() { + [[ -n "$gh" ]] || return + + [[ "$comp" -nt "$gh" ]] || { + local tmp="$comp".tmp."${sysparams[pid]}" + "$gh" completion -s zsh >"$tmp" || return + zf_mv -f -- "$tmp" "$comp" || return + -z4h-compile "$comp" || return + } + + () { + emulate -L zsh + local -h funcstack=(_gh) + builtin source -- "$comp" + } || true + + [[ -v functions[_gh] ]] || return + _comps[gh]=_gh +} || { + builtin unset '_comps[gh]' + _default "$@" + return +} diff --git a/fn/-z4h-complete-helm b/fn/-z4h-complete-helm new file mode 100644 index 0000000..aedba7e --- /dev/null +++ b/fn/-z4h-complete-helm @@ -0,0 +1,32 @@ +#!/usr/bin/env zsh + +local helm="${commands[helm]-}" +local orig_cmd="${_comps[helm]-}" +local comp="$Z4H"/cache/helm-completion-$EUID.zsh +local cmd + +() { + [[ -n "$helm" ]] || return + + [[ "$comp" -nt "$helm" ]] || { + local tmp="$comp".tmp."${sysparams[pid]}" + "$helm" completion zsh >"$tmp" || return + zf_mv -f -- "$tmp" "$comp" || return + -z4h-compile "$comp" || return + } + + () { + emulate -L zsh + builtin source -- "$comp" + unset LWORD RWORD + } || true + + cmd="${_comps[helm]-}" + [[ "$cmd" != "$orig_cmd" ]] || return +} || { + builtin unset '_comps[helm]' + _default "$@" + return +} + +builtin eval "$cmd" diff --git a/fn/-z4h-complete-kitty b/fn/-z4h-complete-kitty new file mode 100644 index 0000000..c910c09 --- /dev/null +++ b/fn/-z4h-complete-kitty @@ -0,0 +1,32 @@ +#!/usr/bin/env zsh + +local kitty="${commands[kitty]-}" +local orig_cmd="${_comps[kitty]-}" +local comp="$Z4H"/cache/kitty-completion-$EUID.zsh +local src="${kitty:h}"/../lib/kitty/kitty/complete.py +local cmd + +() { + [[ -n "$kitty" ]] || return + + [[ "$comp" -nt "$kitty" && (! -e "$src" || "$comp" -nt "$src" ) ]] || { + local tmp="$comp".tmp."${sysparams[pid]}" + "$kitty" + complete setup zsh >"$tmp" || return + zf_mv -f -- "$tmp" "$comp" || return + -z4h-compile "$comp" || return + } + + () { + emulate -L zsh + builtin source -- "$comp" + } || true + + cmd="${_comps[kitty]-}" + [[ "$cmd" != "$orig_cmd" ]] || return +} || { + builtin unset '_comps[kitty]' + _default "$@" + return +} + +builtin eval "$cmd" diff --git a/fn/-z4h-complete-kubectl b/fn/-z4h-complete-kubectl new file mode 100644 index 0000000..3d80139 --- /dev/null +++ b/fn/-z4h-complete-kubectl @@ -0,0 +1,41 @@ +#!/usr/bin/env zsh + +local kubectl="${commands[kubectl]-}" +local orig_cmd="${_comps[kubectl]-}" +local comp="$Z4H"/cache/kubectl-completion-$EUID.zsh +local cmd + +() { + [[ -n "$kubectl" ]] || return + + [[ "$comp" -nt "$kubectl" ]] || { + local tmp="$comp".tmp."${sysparams[pid]}" + "$kubectl" completion zsh >"$tmp" || return + zf_mv -f -- "$tmp" "$comp" || return + -z4h-compile "$comp" || return + } + + () { + emulate -L zsh + if (( ${+functions[_bash_comp]} )); then + builtin source -- "$comp" + else + { + function _bash_comp() {} + builtin source -- "$comp" + } always { + unfunction _bash_comp + } + fi + unset LWORD RWORD + } || true + + cmd="${_comps[kubectl]-}" + [[ "$cmd" != "$orig_cmd" ]] || return +} || { + builtin unset '_comps[kubectl]' + _default "$@" + return +} + +builtin eval "$cmd" diff --git a/fn/-z4h-complete-oc b/fn/-z4h-complete-oc new file mode 100644 index 0000000..6eeaeba --- /dev/null +++ b/fn/-z4h-complete-oc @@ -0,0 +1,41 @@ +#!/usr/bin/env zsh + +local oc="${commands[oc]-}" +local orig_cmd="${_comps[oc]-}" +local comp="$Z4H"/cache/oc-completion-$EUID.zsh +local cmd + +() { + [[ -n "$oc" ]] || return + + [[ "$comp" -nt "$oc" ]] || { + local tmp="$comp".tmp."${sysparams[pid]}" + "$oc" completion zsh >"$tmp" || return + zf_mv -f -- "$tmp" "$comp" || return + -z4h-compile "$comp" || return + } + + () { + emulate -L zsh + if (( ${+functions[_bash_comp]} )); then + builtin source -- "$comp" + else + { + function _bash_comp() {} + builtin source -- "$comp" + } always { + unfunction _bash_comp + } + fi + unset LWORD RWORD + } || true + + cmd="${_comps[oc]-}" + [[ "$cmd" != "$orig_cmd" ]] || return +} || { + builtin unset '_comps[oc]' + _default "$@" + return +} + +builtin eval "$cmd" diff --git a/fn/-z4h-complete-rustup b/fn/-z4h-complete-rustup new file mode 100644 index 0000000..161fe91 --- /dev/null +++ b/fn/-z4h-complete-rustup @@ -0,0 +1,13 @@ +#!/usr/bin/env zsh + +(( $+commands[rustup] )) || { + unset '_comps[rustup]' + _default "$@" + return +} + +{ + builtin source <(rustup completions zsh rustup) +} always { + (( $+functions[_rustup] )) && _comps[rustup]=_rustup || unset '_comps[rustup]' +} diff --git a/fn/-z4h-direnv-hook b/fn/-z4h-direnv-hook new file mode 100644 index 0000000..cd4780a --- /dev/null +++ b/fn/-z4h-direnv-hook @@ -0,0 +1,81 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +unset _z4h_direnv_err + +if (( ARGC )); then + local direnv=$1 +else + local direnv=${commands[direnv]-} + if [[ -z $direnv ]]; then + unset _z4h_direnv_sig + return 1 + fi +fi + +# { print -n '\x1f\x8b\x08\x00\x00\x00\x00\x00'; base64 -d <<<${${DIRENV_WATCHES//-/+}//_//} } | zcat 2>/dev/null + +# zcat +# gzcat +# uncompress -c +# gunzip -c +# gzip -cd + +local sig +local envrc=(./(../)#.envrc(NY1:a)) +if (( $#envrc )); then + local -a deps=( + ${XDG_DATA_HOME:-~/.local/share}/direnv/allow + ${XDG_CONFIG_HOME:-~/.config}/{direnv.toml,config.toml}) + local -a stat + local files=($^deps(N)) + local non_files=(${deps:|files}) + if zstat -A stat +mtime -- $envrc $files 2>/dev/null; then + local sig=$envrc$'\0'${(pj:\0:)stat} + else + local sig=stat-error + fi +elif [[ ! -v DIRENV_WATCHES ]]; then + typeset -g _z4h_direnv_sig=none + return +else + local sig=none +fi + +[[ $sig == ${_z4h_direnv_sig-} ]] && return + +unset _z4h_direnv_sig + +local data +data=$( + local out + out=$($direnv export zsh) 2>&1 + builtin printf '%s%18d%d' "$out" $#out $(( ! $? )) +) || return + +local -i success=$data[-1] +local -i out_len=$data[-19,-2] +local out=$data[-out_len-19,-20] +local err=${data[1,-out_len-20]%%$'\n'#} + +if [[ -n $err ]]; then + local ctx=:z4h:direnv: + (( success )) && ctx+='success' || ctx+='error' + if zstyle -T $ctx notify; then + if builtin zle; then + typeset -g _p9k__raw_msg=${err//\%/%%}$'\n' + else + print -ru2 -- $err + fi + fi +fi + +emulate -L zsh +if [[ ! -v __p9k_trapped ]]; then + local -i __p9k_trapped + builtin trap : INT + builtin trap "builtin trap ${(q)__p9k_trapint:--} INT" EXIT +fi +builtin eval -- $out +typeset -g _z4h_direnv_sig=$sig diff --git a/fn/-z4h-direnv-init b/fn/-z4h-direnv-init new file mode 100644 index 0000000..c6c5f40 --- /dev/null +++ b/fn/-z4h-direnv-init @@ -0,0 +1,32 @@ +#!/usr/bin/env zsh + +local direnv cache=$Z4H/cache/direnv +if [[ -e $cache ]]; then + builtin source -- $cache || return + [[ -n $direnv ]] || return '_z4h_err()' +fi + +if (( $1 )); then + local real_direnv=${commands[direnv]-} + if [[ $direnv != $real_direnv ]]; then + if [[ -n $real_direnv ]]; then + direnv=$real_direnv + if (( ! _z4h_dangerous_root )); then + local tmp=$cache.tmp.$$ + typeset -p direnv >$tmp || return + zf_mv -f -- $tmp $cache || return + fi + else + [[ ! -e $cache ]] || builtin : >$cache || return + return + fi + elif [[ -z $direnv ]]; then + return + fi +elif [[ ! -x $direnv ]]; then + return +fi + +-z4h-direnv-hook $direnv || return + +typeset -gi _z4h_direnv_initialized=1 diff --git a/fn/-z4h-enable-iterm2-integration b/fn/-z4h-enable-iterm2-integration new file mode 100644 index 0000000..9baaf2a --- /dev/null +++ b/fn/-z4h-enable-iterm2-integration @@ -0,0 +1,47 @@ +#!/usr/bin/env zsh + +function iterm2_set_user_var() { + (( ${+commands[base64]} )) || return + local x + x="$(printf '%s' "$2" | command base64 2>/dev/null)" || return + -z4h-tmux-bypass '\e]1337;SetUserVar=%s=%s\007' "$1" "${x//$'\n'}" +} + +if (( ! $+functions[iterm2_print_user_vars] )); then + function iterm2_print_user_vars() { } +fi + +function -z4h-iterm2-dump() { + -z4h-tmux-bypass '\e]1337;RemoteHost=%s@%s\a' "${(%):-%n}" "${iterm2_hostname:-${(%):-%m}}" + # I don't know what iTerm2 wants to be escaped and how. The official shell + # integration doesn't escape anything, which is clearly wrong because \a + # obviously cannot be passed as is. + if [[ $PWD == /* && $PWD -ef . ]]; then + local cwd=$PWD + else + local cwd=${${:-.}:a} + fi + -z4h-tmux-bypass '\e]1337;CurrentDir=%s\a' "${${cwd//$'\a'/\\a}//$'\e'/\\e}" + iterm2_print_user_vars +} + +function -z4h-iterm2-precmd() { + zle && return + if (( _z4h_iterm_cmd )); then + (( _z4h_iterm_cmd == 1 )) && -z4h-tmux-bypass '\e]133;C;\a' + -z4h-tmux-bypass '\e]133;D;%s\a' $1 + fi + -z4h-iterm2-dump + typeset -gi _z4h_iterm_cmd=1 +} + +function -z4h-iterm2-preexec() { + -z4h-tmux-bypass '\e]133;C;\a' + typeset -gi _z4h_iterm_cmd=2 +} + +typeset -g ITERM_SHELL_INTEGRATION_INSTALLED=Yes +typeset -gi _z4h_iterm_cmd=0 # this parameter is read by p10k + +unfunction -- -z4h-enable-iterm2-integration +autoload -Uz -- -z4h-enable-iterm2-integration diff --git a/fn/-z4h-error-command b/fn/-z4h-error-command new file mode 100644 index 0000000..de3b516 --- /dev/null +++ b/fn/-z4h-error-command @@ -0,0 +1,37 @@ +#!/usr/bin/env zsh + +local home=~ +local zdotdir=${${${(q)ZDOTDIR}/#${(q)home}/'~'}//\%/%%} +local z4h=${${${(q)Z4H}/#${(q)home}/'~'}//\%/%%} +print -Pru2 -- '' +print -Pru2 -- '%F{3}z4h%f: %B'${1//\%/%%}'%b %F{1}failed%f' +print -Pru2 -- '' +print -Pru2 -- 'See error messages above to identify the culprit.' +print -Pru2 -- '' +print -Pru2 -- 'Edit Zsh configuration:' +print -Pru2 -- '' +print -Pru2 -- ' %F{2}'${(q)${VISUAL:-${EDITOR:-vi}}}'%f %U'$zdotdir'/.zshrc%u' +if [[ $1 != update ]]; then + print -Pru2 -- '' + print -Pru2 -- 'Retry Zsh initialization:' + print -Pru2 -- '' + print -Pru2 -- ' %U%F{2}exec%u zsh%f' +fi +print -Pru2 -- '' +print -Pru2 -- 'If nothing helps and you are about to give up:' +print -Pru2 -- '' +print -Pru2 -- ' %F{5}# nuke the entire site from orbit' +print -Pru2 -- ' %F{2}%Usudo%u rm%f -rf -- %U'$z4h'%u' +if (( $+commands[curl] )); then + print -Pru2 -- '' + print -Pru2 -- 'Give up and start over:' + print -Pru2 -- '' + print -Pru2 -- ' %F{2}sh%f -c %F{3}"%f$(%F{2}curl%f -fsSL %Uhttps://raw.githubusercontent.com/romkatv/zsh4humans/v5/install%u)%F{3}"%f' + print -Pru2 -- '' +elif (( $+commands[wget] )); then + print -Pru2 -- '' + print -Pru2 -- 'Give up and start over:' + print -Pru2 -- '' + print -Pru2 -- ' %F{2}sh%f -c %F{3}"%f$(%F{2}wget%f -O- %Uhttps://raw.githubusercontent.com/romkatv/zsh4humans/v5/install%u)%F{3}"%f' + print -Pru2 -- '' +fi diff --git a/fn/-z4h-error-iterm2-integration b/fn/-z4h-error-iterm2-integration new file mode 100644 index 0000000..38c4872 --- /dev/null +++ b/fn/-z4h-error-iterm2-integration @@ -0,0 +1,23 @@ +#!/usr/bin/env zsh + +local home=~ +local zdotdir=${${${(q)ZDOTDIR}/#${(q)home}/'~'}//\%/%%} + +if [[ ! -t 2 ]]; then + (( $+functions[p10k] )) && p10k clear-instant-prompt +fi + +print -Pru2 -- '%F{3}z4h%f: iTerm2 integration loaded incorrectly!' +print -Pru2 -- '' +print -Pru2 -- 'To fix this issue:' +print -Pru2 -- '' +print -Pru2 -- '1. Add this line at the top of %U'$zdotdir'/.zshrc%u:' +print -Pru2 -- '' +print -Pru2 -- " %F{2}zstyle%f %F{3}':z4h:'%f term-shell-integration %F{3}'yes'%f" +print -Pru2 -- '' +print -Pru2 -- '2. (Optional) Remove the following line (or similar) from %U'$zdotdir'/.zshrc%u:' +print -Pru2 -- '' +print -Pru2 -- ' %F{2}source%f %U~/.iterm2_shell_integration.zsh%u' + +unfunction -- -z4h-error-iterm2-integration +autoload -Uz -- -z4h-error-iterm2-integration diff --git a/fn/-z4h-error-param-changed b/fn/-z4h-error-param-changed new file mode 100644 index 0000000..26f8849 --- /dev/null +++ b/fn/-z4h-error-param-changed @@ -0,0 +1,19 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +print -Pru2 -- "%F{3}z4h%f: core parameters have unexpectedly changed" +local -a old=(${(0)_z4h_param_sig}) +local -a new=(${(0)${(e)_z4h_param_pat}}) +local diff_old=(${new:|old}) +local diff_new=(${old:|new}) +print -Pru2 -- "" +print -Pru2 -- "%F{2}Expected:%f" +print -Pru2 -- "" +print -lru2 -- " "$^diff_old +print -Pru2 -- "" +print -Pru2 -- "%F{1}Found:%f" +print -Pru2 -- "" +print -lru2 -- " "$^diff_new +print -Pru2 -- "" +print -Pru2 -- "Restore the parameters or restart Zsh with %F{2}%Uexec%u zsh%f." diff --git a/fn/-z4h-find b/fn/-z4h-find new file mode 100644 index 0000000..7f71143 --- /dev/null +++ b/fn/-z4h-find @@ -0,0 +1,58 @@ +#!/usr/bin/env zsh + +local dot_glob=$1 +local only_dirs=$2 +local dirs=("${@:3}") + +(( $#dirs )) || return 0 + +local -a bin +local -a flags +() { + emulate -L zsh + # Set dot_glob in case the value of find-flags depends on it (via `zstyle -e`). + # Ideally we should run this with user options. + (( dot_glob )) && setopt dot_glob + local widget=${WIDGET#z4h-} + zstyle -a :z4h:$widget find-command bin + if (( ! $#bin )); then + if (( $+commands[bfs] )); then + bin=(command bfs) + else + bin=(command find) + fi + fi + zstyle -a :z4h:$widget find-flags flags + if (( ! $#flags )); then + flags=(-name '.*' -prune -print -o -print) + fi +} + +local -a cmd +local -aU fss +fss=(${(f)"$("${bin[@]}" / . -maxdepth 0 -printf '%F\n' 2>/dev/null)"}) || fss=() +if (( $#fss )) && [[ -z ${(M)fss:#unknown} ]]; then + cmd+=("${bin[@]}" -L ./$^dirs) + (( only_dirs )) && cmd+=('!' -type d -prune -o) + cmd+=('!' '(') + local fs + for fs in $fss; do + cmd+=(-fstype $fs -o) + done + cmd[-1]=(')' -prune '(' "${flags[@]}" ')') + (( dot_glob )) || cmd+=(-o -name '.*' -prune) + cmd+=(-o "${flags[@]}") +else + cmd+=("${bin[@]}" -L . -xdev -mindepth 1) + (( only_dirs )) && cmd+=('!' -type d -prune -o) + cmd+=('!' -path './*/*' '!' '(') + local dir + for dir in $dirs; do + cmd+=(-name ${(b)dir} -o) + done + cmd[-1]=(')' -prune) + (( dot_glob )) || cmd+=(-o -name '.*' -prune) + cmd+=(-o "${flags[@]}") +fi + +"${cmd[@]}" diff --git a/fn/-z4h-find-prev-zword b/fn/-z4h-find-prev-zword new file mode 100644 index 0000000..4510a99 --- /dev/null +++ b/fn/-z4h-find-prev-zword @@ -0,0 +1,21 @@ +#!/usr/bin/env zsh +# +# Usage: -z4h-find-prev-zword +# +# Sets reply=(START END) such that BUFFER[START,END] contains the shell word +# that the cursor is on, or the previous shell word if the cursor is on +# whitespace. If there are no shell words in $PREBUFFER$BUFFER, return 1 +# without setting reply. START and END can be non-positive, which means that +# a part of the shell word is in PREBUFFER. + +emulate -L zsh -o extended_glob +local word buf=$PREBUFFER$BUFFER +for word in '' ${(Z:n:)buf}; do + tail=${${buf:$#word}##[[:space:]]#} + (( ! $#tail || $#tail < $#RBUFFER )) && break + buf=$tail +done +[[ -n $word ]] || return +local -i start=$(($#BUFFER - $#buf + 1)) +local -i end=$((start + $#word - 1)) +typeset -g reply=($start $end) diff --git a/fn/-z4h-fix-locale b/fn/-z4h-fix-locale new file mode 100644 index 0000000..4feb08d --- /dev/null +++ b/fn/-z4h-fix-locale @@ -0,0 +1,7 @@ +#!/usr/bin/env zsh + +# Try in order: C.UTF-8, en_US.UTF-8, the first UTF-8 locale in lexicographical order. +(( $+commands[locale] )) || return +local loc=(${(@M)$(locale -a):#*.(utf|UTF)(-|)8}) +(( $#loc )) || return +export LC_ALL=${loc[(r)(#i)C.UTF(-|)8]:-${loc[(r)(#i)en_US.UTF(-|)8]:-$loc[1]}} diff --git a/fn/-z4h-flowing b/fn/-z4h-flowing new file mode 100644 index 0000000..aa7f700 --- /dev/null +++ b/fn/-z4h-flowing @@ -0,0 +1,30 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +local -a centered indentation +zparseopts -D -F -- c=centered i:=indentation || return '_z4h_err()' +local -i centered=$#centered +local -i indentation=$(( indentation[2] )) +local -i columns='COLUMNS > 0 && COLUMNS <= 80 ? COLUMNS: 80' + +local REPLY line word lines=() +for word in "$@"; do + -z4h-prompt-length ${(g::):-"$line $word"} + if (( REPLY > columns )); then + [[ -z $line ]] || lines+=$line + line= + fi + if [[ -n $line ]]; then + line+=' ' + elif (( $#lines )); then + line=${(pl:$indentation:: :)} + fi + line+=$word +done +[[ -z $line ]] || lines+=$line +for line in $lines; do + -z4h-prompt-length ${(g::)line} + (( centered && REPLY < columns )) && print -n -- ${(pl:$(((columns - REPLY) / 2)):: :)} + print -P -- $line +done diff --git a/fn/-z4h-fzf b/fn/-z4h-fzf new file mode 100644 index 0000000..8a58743 --- /dev/null +++ b/fn/-z4h-fzf @@ -0,0 +1,64 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +local widget=${WIDGET#z4h-} + +local -a cmd flags bindings +zstyle -a :z4h:$widget fzf-command cmd || cmd=($Z4H/fzf/bin/fzf) +zstyle -a :z4h:$widget fzf-flags flags || flags=() +zstyle -a :z4h:$widget fzf-bindings bindings || bindings=() + +local -A keys=( + ctrl-h backward-kill-word + alt-j clear-query + ctrl-u clear-query + ctrl-k kill-line + alt-k unix-line-discard + ctrl-space toggle-out + ctrl-a toggle-all +) + +if (( ${@[(I)--layout=default]} )); then + keys+=( + tab up + btab down + ctrl-r up + ctrl-s down + ) +else + keys+=( + tab down + btab up + ctrl-r down + ctrl-s up + ) +fi + +local kv +for kv in $bindings; do + kv=(${(s.:.)kv}) + (( $#kv == 2 )) || continue + keys[$kv[1]]=$kv[2] +done + +local k v +local -i expect +for k v in ${(kv)keys}; do + if [[ +$v+ == *+repeat+* ]]; then + flags=(--expect=$k "${flags[@]}") + expect=1 + if [[ $v == repeat(|+*) ]]; then + keys[$k]=ignore + else + keys[$k]=${v%%+repeat(|+*)} + fi + fi +done + +local keymap +printf -v keymap '%s:%s,' ${(kv)keys} +local out=$("${cmd[@]}" "$@" --bind=${keymap%,} "${flags[@]}") +[[ -n $out ]] || return +(( expect )) || print +print -r -- $out diff --git a/fn/-z4h-gen-init-darwin-paths b/fn/-z4h-gen-init-darwin-paths new file mode 100644 index 0000000..16a896f --- /dev/null +++ b/fn/-z4h-gen-init-darwin-paths @@ -0,0 +1,32 @@ +#!/usr/bin/env zsh + +local -a deps=( + '/usr/libexec/path_helper' + '/etc/paths(N)' + '/etc/paths.d/*(N)' + '/etc/manpaths(N)' + '/etc/manpaths.d/*(N)' +) + +local -a stat files=($~deps) +zstat -A stat +mtime -- $files 2>/dev/null || return + +local script +script="$(PATH= MANPATH= $deps[1] -s)" || return + +( + eval "$script" || return + >$Z4H/cache/init-darwin-paths.tmp.$$ print -r -- '() { + local deps=('${(@q)deps}') + local -a stat files=($~deps) + [[ ${(j: :)${(@q)files}} == '${(q)${(j: :)${(@q)files}}}' ]] && + zstat -A stat +mtime -- $files 2>/dev/null && + [[ ${(j: :)stat} == '${(q)${(j: :)stat}}' ]] && { + local path_dirs=('${(j: :)${(@q)path}}') + local manpath_dirs=('${(j: :)${(@q)manpath}}') + path=(${path_dirs:|path} $path) + manpath=(${manpath_dirs:|manpath} $manpath) + } +}' || return + zf_mv -f -- $Z4H/cache/init-darwin-paths.tmp.$$ $Z4H/cache/init-darwin-paths +) diff --git a/fn/-z4h-get-cursor-pos b/fn/-z4h-get-cursor-pos new file mode 100644 index 0000000..7af2009 --- /dev/null +++ b/fn/-z4h-get-cursor-pos @@ -0,0 +1,24 @@ +#!/usr/bin/env zsh +# +# If invoked with an argument, passes unrecognized TTY content to zle. + +local fd=${_z4h_tty_fd-1} +[[ -t $fd ]] || return + +# Note: `read -u $fd` doesn't work. + +local resp +IFS= builtin read -srt 5 -d R resp$'?\e[6n' <&$fd || return + +while [[ $resp != *$'\e['<->';'<-> ]]; do + IFS= builtin read -srt 5 -d R resp <&$fd || return +done + +if (( ARGC )); then + local pref=${resp%$'\e['*} + [[ -n $pref ]] && print -rz -- $pref +fi + +resp=${resp##*'['} +typeset -g cursor_y=${resp%';'*} +typeset -g cursor_x=${resp#*';'} diff --git a/fn/-z4h-help-bindkey b/fn/-z4h-help-bindkey new file mode 100644 index 0000000..18bf24d --- /dev/null +++ b/fn/-z4h-help-bindkey @@ -0,0 +1,8 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +print -Pr -- "$(<<\END +Usage: %F{2}z4h%f %Bbindkey%b [--] zle-widget key-binding [key-binding]... +END +)" diff --git a/fn/-z4h-help-compile b/fn/-z4h-help-compile new file mode 100644 index 0000000..73de296 --- /dev/null +++ b/fn/-z4h-help-compile @@ -0,0 +1,36 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +print -Pr -- "$(<<\END +Usage: %F{2}z4h%f %Bcompile%b [--] [%Ufile%u]... + +Compile the specified Zsh files with %F{2}zcompile%f, skipping over +inexisting files. It takes less time to %F{2}source%f a file that thas +previously been compiled. + +If you want to source files immediately after compiling them, +use %F{2}z4h%f %Bsource%b with %B--compile%b instead. + +Exit code of %F{2}z4h%f %Bcompile%b is that of the last %F{2}zcompile%f call. +Inexisting files do not affect exit code. + +Note that sourcing compiled files may have a different effect +than plain sourcing. Namely, aliases defined within a sourced +file are not expanded in that same file when it's compiled. +For example: + + %% %F{2}cat%f %Uconfig.zsh%u + + %F{2}alias%f echo=%F{3}'echo hello'%f + %F{2}echo%f world + + %% ( %F{2}source%f %Uconfig.zsh%u ) + + hello world + + %% ( %F{2}z4h%f compile %Uconfig.zsh%u; %F{2}source%f %Uconfig.zsh%u ) + + world +END +)" diff --git a/fn/-z4h-help-load b/fn/-z4h-help-load new file mode 100644 index 0000000..70f69ac --- /dev/null +++ b/fn/-z4h-help-load @@ -0,0 +1,25 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +print -Pr -- "$(<<\END +Usage: %F{2}z4h%f %Bload%b [-c|--compile] [--] [%Udir%u]... + +Load plugins in Oh My Zsh or Prezto format skipping over +inexisting directories. If %B-c%b or %B--compile%b is specified, +plugin source files are automatically compiled with %F{2}zcompile%f +to speed up subsequent loading. This gives you faster Zsh +startup. + +Relative directory names are resolved from %U$Z4H/%u. This is +the directory where %F{2}z4h%f %Binstall%b puts downloaded files. + + %F{2}z4h%f %Binstall%b ohmyzsh/ohmyzsh + ... + %F{2}z4h%f %Bload%b ohmyzsh/ohmyzsh/plugins/emoji-clock + +Exit code of %F{2}z4h%f %Bload%b is that of sourcing the last plugin's +source file. Inexisting plugin directories do not affect +exit code. +END +)" diff --git a/fn/-z4h-help-source b/fn/-z4h-help-source new file mode 100644 index 0000000..9be1ab3 --- /dev/null +++ b/fn/-z4h-help-source @@ -0,0 +1,41 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +print -Pr -- "$(<<\END +Usage: %F{2}z4h%f %Bsource%b [-c|--compile] [--] [%Ufile%u]... + +Source the specified Zsh files, skipping over inexisting ones. +If %B-c%b or %B--compile%b is specified, files are automatically +compiled with %F{2}zcompile%f to speed up subsequent sourcing. This +gives you faster Zsh startup. + +Relative file names are resolved from %U$Z4H/%u. This is the +directory where %F{2}z4h%f %Binstall%b puts downloaded files. + + %F{2}z4h%f %Binstall%b ohmyzsh/ohmyzsh + ... + %F{2}z4h%f %Bsource%b ohmyzsh/ohmyzsh/lib/diagnostics.zsh + +Exit code of %F{2}z4h%f %Bsource%b is that of the last sourced file. +Inexisting files do not affect exit code. + +Compilation isn't enabled by default because it has one +negative side effect. Namely, aliases defined within a +sourced file are not expanded in that same file. For +example: + + %% %F{2}cat%f %U~/config.zsh%u + + %F{2}alias%f echo=%F{3}'echo hello'%f + %F{2}echo%f world + + %% ( %F{2}z4h%f source %U~/config.zsh%u ) + + hello world + + %% ( %F{2}z4h%f source -c %U~/config.zsh%u ) + + world +END +)" diff --git a/fn/-z4h-help-ssh b/fn/-z4h-help-ssh new file mode 100644 index 0000000..0065fc3 --- /dev/null +++ b/fn/-z4h-help-ssh @@ -0,0 +1,22 @@ +#!/usr/bin/env zsh + +print -Pr -- "$(<<\END +Usage: %F{2}z4h%f %Bssh%b [%Ussh-options%u]... [--] [%Uuser@%u]%Uhostname%u + +Connect to the remote host over SSH and start Zsh with local configs. +The remote host must have internet access, standard POSIX utilities +and directories, and a login shell compatible with the Bourne shell +(sh, bash, zsh, ash, dash, etc.) or csh/tcsh. Nothing else is required. + +Here's what %F{2}z4h%f %Bssh%b does in more detail: + + 1. Archives Zsh config files on the local host and sends them to the + remote host. + 2. Extracts Zsh config files on the remote host. + 3. Sources %U.zshenv%u with Z4H_BOOTSRAPING=%F{3}'1'%f, which starts the usual + %Bzsh4humans%b bootstrap process. + +The first login to a remote host may take some time. After that it's +as fast as normal %F{2}ssh%f. +END +)" diff --git a/fn/-z4h-init b/fn/-z4h-init new file mode 100644 index 0000000..4f4c6aa --- /dev/null +++ b/fn/-z4h-init @@ -0,0 +1,136 @@ +#!/usr/bin/env zsh + +autoload -Uz add-zsh-hook || return + +if [[ $commands[zsh] != $_z4h_exe ]]; then + export _Z4H_EXE=$_z4h_exe + path=($Z4H/zsh4humans/zb $path) +fi + +# Use lesspipe if available. It allows you to use less on binary files (*.tar.gz, *.jpg, etc.). +if (( $+commands[lesspipe] || $+commands[lesspipe.sh] )); then + export LESSOPEN="| /usr/bin/env ${(q)${commands[lesspipe]:-${commands[lesspipe.sh]}}} %s 2>/dev/null" +fi + +# This affects every invocation of `less`. +# +# -i case-insensitive search unless search string contains uppercase letters +# -R color +# -F exit if there is less than one page of content +# -X keep content on screen after exit +# -M show more info at the bottom prompt line +# -x4 tabs are 4 instead of 8 +export LESS='-iRFXMx4' + +(( $+commands[less] )) && export PAGER=less + +# LS_COLORS is used by GNU ls and Zsh completions. LSCOLORS is used by BSD ls. +export LS_COLORS='fi=00:mi=00:mh=00:ln=01;36:or=01;31:di=01;34:ow=04;01;34:st=34:tw=04;34:' +LS_COLORS+='pi=01;33:so=01;33:do=01;33:bd=01;33:cd=01;33:su=01;35:sg=01;35:ca=01;35:ex=01;32' +export LSCOLORS='ExGxDxDxCxDxDxFxFxexEx' + +# TREE_COLORS is used by GNU tree. It looks awful with underlined text, so we turn it off. +export TREE_COLORS=${LS_COLORS//04;} + +if [[ $OSTYPE == linux-* && -r /proc/version && + ( -v commands[cmd.exe] || -e /mnt/c/Windows/System32/cmd.exe ) ]]; then + -z4h-init-wsl +fi + +if (( $+commands[less] )); then + READNULLCMD=less +elif (( $+commands[more] )); then + READNULLCMD=more +elif (( $+commands[cat] )); then + READNULLCMD=cat +fi + +TIMEFMT='user=%U system=%S cpu=%P total=%*E' # output format of `time` reserved word + +: ${DIRSTACKSIZE:=10000} + +zstyle -t :z4h:ssh-agent: start && -z4h-start-ssh-agent + +function -z4h-wrap-commands() { + emulate -L zsh + if (( _z4h_wrap_ssh != 2 )); then + if (( _z4h_wrap_ssh )); then + if [[ ! -v functions[ssh] ]]; then + typeset -gi _z4h_wrap_ssh=2 + elif [[ ! -v commands[ssh] ]]; then + unset _z4h_wrap_ssh + unfunction ssh + fi + else + if [[ -v functions[ssh] ]]; then + typeset -gi _z4h_wrap_ssh=2 + elif [[ -v commands[ssh] ]]; then + function ssh() { z4h ssh "$@" } + typeset -gi _z4h_wrap_ssh=1 + fi + fi + fi + + if (( _z4h_wrap_sudo != 2 )); then + if (( _z4h_wrap_sudo )); then + if [[ ! -v functions[sudo] ]]; then + typeset -gi _z4h_wrap_sudo=2 + elif [[ ! -v commands[sudo] ]]; then + unset _z4h_wrap_sudo + unfunction sudo + fi + else + if [[ -v functions[sudo] ]]; then + typeset -gi _z4h_wrap_sudo=2 + elif [[ -v commands[sudo] ]]; then + function sudo() { z4h sudo "$@" } + typeset -gi _z4h_wrap_sudo=1 + fi + fi + fi + + if (( _z4h_wrap_docker != 2 )); then + if (( _z4h_wrap_docker )); then + if [[ ! -v functions[docker] ]]; then + typeset -gi _z4h_wrap_docker=2 + elif [[ ! -v commands[docker] ]]; then + unset _z4h_wrap_docker + unfunction docker + fi + else + if [[ -v functions[docker] ]]; then + typeset -gi _z4h_wrap_docker=2 + elif [[ -v commands[docker] ]]; then + function docker() { z4h docker "$@" } + typeset -gi _z4h_wrap_docker=1 + fi + fi + fi +} + +-z4h-wrap-commands + +if [[ ! -e $Z4H/zsh4humans/fn/-z4h-compinit-impl ]]; then + -z4h-postinstall-self || return +fi + +if (( _z4h_zle )); then + -z4h-init-zle || return +else + -z4h-cmd-bindkey() {} + function compdef() {} + function complete() {} +fi + +function compinit() {} +function bashcompinit() {} + +# Aliases. +if (( $+commands[dircolors] )); then # proxy for GNU coreutils vs BSD + # Don't define aliases for commands that point to busybox. + [[ ${${:-diff}:c:A:t} == busybox* ]] || alias diff='diff --color=auto' + [[ ${${:-ls}:c:A:t} == busybox* ]] || alias ls='ls --color=auto' +else + [[ ${${:-ls}:c:A:t} == busybox* ]] || alias ls='ls -G' +fi +[[ ${${:-grep}:c:A:t} == busybox* ]] || alias grep='grep --color=auto --exclude-dir={.bzr,CVS,.git,.hg,.svn}' diff --git a/fn/-z4h-init-wsl b/fn/-z4h-init-wsl new file mode 100644 index 0000000..40703b2 --- /dev/null +++ b/fn/-z4h-init-wsl @@ -0,0 +1,23 @@ +#!/usr/bin/env zsh + +local cmd=${commands[cmd.exe]:-/mnt/c/Windows/System32/cmd.exe} +[[ -x $cmd ]] || return + +case "$(/dev/null)//$'\r'}}") +local keys=(${lines%%=*}) vals=(${lines#*=}) +typeset -grA z4h_win_env=(${keys:^vals}) +local home=$z4h_win_env[USERPROFILE] +[[ -n $home ]] || return +if [[ $home != [a-zA-Z]':\'* || + ( ! -d ${home::=/mnt/${(L)home[1]}/${${home:3}//\\//}} && + ! -d ${home::=${home#/mnt}} ) ]]; then + home=$(command wslpath -- $z4h_win_env[USERPROFILE] 2>/dev/null) || return + [[ -d $home ]] || return +fi +typeset -gr z4h_win_home=$home diff --git a/fn/-z4h-init-zle b/fn/-z4h-init-zle new file mode 100644 index 0000000..e6170ee --- /dev/null +++ b/fn/-z4h-init-zle @@ -0,0 +1,1103 @@ +#!/usr/bin/env zsh + +if { zstyle -t :z4h: term-shell-integration || zstyle -t :z4h: iterm2-integration } && + [[ $TERM != (dumb|linux) && $ITERM_SHELL_INTEGRATION_INSTALLED != Yes && + -z $NVIM_LISTEN_ADDRESS && -z $NVIM ]]; then + -z4h-enable-iterm2-integration +else + function -z4h-iterm2-precmd() { } + function -z4h-iterm2-preexec() { } +fi + +# When a command is running, display it in the terminal title. +function -z4h-set-term-title-preexec() { + emulate -L zsh + local _z4h_fmt + if [[ -n $SSH_CONNECTION || $P9K_SSH == 1 ]]; then + zstyle -s :z4h:term-title:ssh preexec _z4h_fmt || _z4h_fmt='%n@%m: ${1//\%/%%}' + else + zstyle -s :z4h:term-title:local preexec _z4h_fmt || _z4h_fmt='${1//\%/%%}' + fi + -z4h-iterm2-preexec + [[ -z $_z4h_fmt ]] || -z4h-set-term-title $_z4h_fmt "$@" +} + +# When no command is running, display the current directory in the terminal title. +function -z4h-set-term-title-precmd() { + local -i err=$? + emulate -L zsh + local _z4h_fmt + if [[ -n $SSH_CONNECTION || $P9K_SSH == 1 ]]; then + zstyle -s :z4h:term-title:ssh precmd _z4h_fmt || _z4h_fmt='%n@%m: %~' + else + zstyle -s :z4h:term-title:local precmd _z4h_fmt || _z4h_fmt='%~' + fi + -z4h-iterm2-precmd $err + [[ -z $_z4h_fmt ]] || -z4h-set-term-title $_z4h_fmt +} + +autoload -Uz up-line-or-beginning-search down-line-or-beginning-search || return +autoload -Uz run-help ${^fpath}/run-help-^*.zwc(N:t) || return +add-zsh-hook -- preexec -z4h-set-term-title-preexec || return +add-zsh-hook -- precmd -z4h-set-term-title-precmd || return +[[ -v aliases[run-help] ]] && unalias run-help + +local pkg +typeset -gA _z4h_use +for pkg in fzf powerlevel10k systemd zsh-history-substring-search \ + zsh-completions zsh-autosuggestions zsh-syntax-highlighting; do + zstyle -t :z4h:$pkg channel none || _z4h_use[$pkg]=1 +done +typeset -grA _z4h_use + +if (( _z4h_use[powerlevel10k] && ! $#POWERLEVEL9K_CONFIG_FILE )) ; then + POWERLEVEL9K_CONFIG_FILE=$ZDOTDIR/.p10k + if [[ $langinfo[CODESET] != (utf|UTF)(-|)8 || $TERM == (dumb|linux) ]]; then + POWERLEVEL9K_CONFIG_FILE+=-ascii + fi + (( terminfo[colors] >= 256 )) || POWERLEVEL9K_CONFIG_FILE+=-8color + POWERLEVEL9K_CONFIG_FILE+=.zsh +fi + +# Enable command_not_found_handler if possible. +if [[ -v functions[command_not_found_handler] ]]; then + # Already installed or not needed. +elif [[ -v commands[brew] && + -n $HOMEBREW_REPOSITORY && + -e $HOMEBREW_REPOSITORY/Library/Taps/homebrew/homebrew-command-not-found/cmd/which-formula.rb || + -x /usr/lib/command-not-found ]]; then + autoload +X -Uz -- -z4h-command-not-found + builtin functions -c -- -z4h-command-not-found command_not_found_handler +elif [[ -e /etc/zsh_command_not_found ]]; then + builtin source /etc/zsh_command_not_found +elif [[ -e /usr/share/doc/pkgfile/command-not-found.zsh ]]; then + builtin source /usr/share/doc/pkgfile/command-not-found.zsh +elif [[ -x /usr/libexec/pk-command-not-found && -S /var/run/dbus/system_bus_socket ]]; then + command_not_found_handler() { /usr/libexec/pk-command-not-found "$@" } +elif [[ -x /data/data/com.termux/files/usr/libexec/termux/command-not-found ]]; then + command_not_found_handler() { /data/data/com.termux/files/usr/libexec/termux/command-not-found "$1" } +elif [[ -x /run/current-system/sw/bin/command-not-found ]]; then + command_not_found_handler() { /run/current-system/sw/bin/command-not-found "$@" } +fi + +function z4h-up-prefix-local() { + -z4h-with-local-history 1 up-line-or-beginning-search "$@" +} +function z4h-down-prefix-local() { + -z4h-with-local-history 1 down-line-or-beginning-search "$@" +} +function z4h-up-prefix-global() { + -z4h-with-local-history 0 up-line-or-beginning-search "$@" +} +function z4h-down-prefix-global() { + -z4h-with-local-history 0 down-line-or-beginning-search "$@" +} + +if (( _z4h_use[zsh-history-substring-search] )); then + function z4h-up-substring-local() { + -z4h-with-local-history 1 history-substring-search-up "$@" + } + function z4h-down-substring-local() { + -z4h-with-local-history 1 history-substring-search-down "$@" + } + function z4h-up-substring-global() { + -z4h-with-local-history 0 history-substring-search-up "$@" + } + function z4h-down-substring-global() { + -z4h-with-local-history 0 history-substring-search-down "$@" + } +fi + +function z4h-kill-word() { -z4h-move-and-kill z4h-forward-word } +function z4h-backward-kill-word() { -z4h-move-and-kill z4h-backward-word } +function z4h-kill-zword() { -z4h-move-and-kill z4h-forward-zword } +function z4h-backward-kill-zword() { -z4h-move-and-kill z4h-backward-zword } + +function z4h-beginning-of-buffer() { CURSOR=0 } +function z4h-end-of-buffer() { + typeset -gi CURSOR='_z4h_cursor_max()' +} +function z4h-expand() { zle _expand_alias || zle .expand-word || true } + +function z4h-cd-back() { -z4h-cd-rotate +1 } +function z4h-cd-forward() { -z4h-cd-rotate -0 } +function z4h-cd-up() { builtin cd -q .. && -z4h-redraw-prompt } + +function z4h-do-nothing() {} + +if (( $+terminfo[civis] && $+terminfo[cnorm] )) && [[ -v _z4h_tty_fd ]]; then + function -z4h-cursor-hide() { + [[ -t 1 ]] && echoti civis || echoti civis >&$_z4h_tty_fd + } + function -z4h-cursor-show() { + # See https://github.com/romkatv/powerlevel10k/issues/1699. + local cnorm=${${terminfo[cnorm]-}:/*$'\e[?25h'(|'\e'*)/$'\e[?25h'} + [[ -t 1 ]] && print -rn -- $cnorm || print -rnu $_z4h_tty_fd -- $cnorm + } +else + function -z4h-cursor-hide() {} + function -z4h-cursor-show() {} +fi + +functions -M -- _z4h_cursor_max 0 0 + +zle -N up-line-or-beginning-search +zle -N down-line-or-beginning-search +zle -N z4h-accept-line +zle -N z4h-eof +zle -N z4h-exit +zle -N z4h-expand +zle -N z4h-forward-word +zle -N z4h-kill-word +zle -N z4h-backward-word +zle -N z4h-backward-kill-word +zle -N z4h-forward-zword +zle -N z4h-kill-zword +zle -N z4h-backward-zword +zle -N z4h-backward-kill-zword +zle -N z4h-quote-prev-zword +zle -N z4h-beginning-of-buffer +zle -N z4h-end-of-buffer +zle -N z4h-stash-buffer +zle -N z4h-fzf-complete +zle -N z4h-up-prefix-local +zle -N z4h-down-prefix-local +zle -N z4h-up-prefix-global +zle -N z4h-down-prefix-global +if (( _z4h_use[zsh-history-substring-search] )); then + zle -N z4h-up-substring-local + zle -N z4h-down-substring-local + zle -N z4h-up-substring-global + zle -N z4h-down-substring-global +fi +zle -N z4h-cd-back +zle -N z4h-cd-forward +zle -N z4h-cd-up +zle -N z4h-cd-down +zle -N z4h-fzf-history +zle -N z4h-fzf-dir-history +zle -N z4h-autosuggest-accept +zle -N z4h-do-nothing +zle -N z4h-clear-screen-soft-top +zle -N z4h-clear-screen-soft-bottom +zle -N z4h-clear-screen-hard-top +zle -N z4h-clear-screen-hard-bottom + +zle -C -- -z4h-comp-insert-all complete-word -z4h-comp-insert-all + +PROMPT_EOL_MARK='%K{red} %k' # mark the missing \n at the end of a comand output with a red block +WORDCHARS='' # only alphanums make up words in word-based zle widgets +ZLE_REMOVE_SUFFIX_CHARS='' # don't eat space when typing '|' after a tab completion +KEYTIMEOUT=20 # wait for 200ms for the continuation of a key sequence +zle_highlight=('paste:none') # disable highlighting of text pasted into the command line + +if (( ! _z4h_custom_histfile )); then + HISTFILE=${ZDOTDIR:-~}/.zsh_history +fi +HISTSIZE=1000000000 # infinite command history +SAVEHIST=1000000000 # infinite command history + +# Delete all existing keymaps and reset to the default state. +bindkey -d +bindkey -e + +local keymap +for keymap in emacs viins vicmd; do + # If NumLock is off, translate keys to make them appear the same as with NumLock on. + bindkey -M $keymap -s '^[OM' '^M' # enter + bindkey -M $keymap -s '^[OX' '=' + bindkey -M $keymap -s '^[Oj' '*' + bindkey -M $keymap -s '^[Ok' '+' + bindkey -M $keymap -s '^[Ol' '+' + bindkey -M $keymap -s '^[Om' '-' + bindkey -M $keymap -s '^[On' '.' + bindkey -M $keymap -s '^[Oo' '/' + bindkey -M $keymap -s '^[Op' '0' + bindkey -M $keymap -s '^[Oq' '1' + bindkey -M $keymap -s '^[Or' '2' + bindkey -M $keymap -s '^[Os' '3' + bindkey -M $keymap -s '^[Ot' '4' + bindkey -M $keymap -s '^[Ou' '5' + bindkey -M $keymap -s '^[Ov' '6' + bindkey -M $keymap -s '^[Ow' '7' + bindkey -M $keymap -s '^[Ox' '8' + bindkey -M $keymap -s '^[Oy' '9' + + # If someone switches our terminal to application mode (smkx), translate keys to make + # them appear the same as in raw mode (rmkx). + bindkey -M $keymap -s '^[OA' '^[[A' # up + bindkey -M $keymap -s '^[OB' '^[[B' # down + bindkey -M $keymap -s '^[OD' '^[[D' # left + bindkey -M $keymap -s '^[OC' '^[[C' # right + bindkey -M $keymap -s '^[OH' '^[[H' # home + bindkey -M $keymap -s '^[OF' '^[[F' # end + + # TTY sends different key codes. Translate them to xterm equivalents. + # Missing: {ctrl,alt,shift}+{up,down,left,right,home,end}, {ctrl,alt}+delete. + bindkey -M $keymap -s '^[[1~' '^[[H' # home + bindkey -M $keymap -s '^[[4~' '^[[F' # end + + # Urxvt sends different key codes. Translate them to xterm equivalents. + bindkey -M $keymap -s '^[[7~' '^[[H' # home + bindkey -M $keymap -s '^[[8~' '^[[F' # end + bindkey -M $keymap -s '^[Oa' '^[[1;5A' # ctrl+up + bindkey -M $keymap -s '^[Ob' '^[[1;5B' # ctrl+down + bindkey -M $keymap -s '^[Od' '^[[1;5D' # ctrl+left + bindkey -M $keymap -s '^[Oc' '^[[1;5C' # ctrl+right + bindkey -M $keymap -s '^[[7\^' '^[[1;5H' # ctrl+home + bindkey -M $keymap -s '^[[8\^' '^[[1;5F' # ctrl+end + bindkey -M $keymap -s '^[[3\^' '^[[3;5~' # ctrl+delete + bindkey -M $keymap -s '^[^[[A' '^[[1;3A' # alt+up + bindkey -M $keymap -s '^[^[[B' '^[[1;3B' # alt+down + bindkey -M $keymap -s '^[^[[D' '^[[1;3D' # alt+left + bindkey -M $keymap -s '^[^[[C' '^[[1;3C' # alt+right + bindkey -M $keymap -s '^[^[[7~' '^[[1;3H' # alt+home + bindkey -M $keymap -s '^[^[[8~' '^[[1;3F' # alt+end + bindkey -M $keymap -s '^[^[[3~' '^[[3;3~' # alt+delete + bindkey -M $keymap -s '^[[a' '^[[1;2A' # shift+up + bindkey -M $keymap -s '^[[b' '^[[1;2B' # shift+down + bindkey -M $keymap -s '^[[d' '^[[1;2D' # shift+left + bindkey -M $keymap -s '^[[c' '^[[1;2C' # shift+right + bindkey -M $keymap -s '^[[7$' '^[[1;2H' # shift+home + bindkey -M $keymap -s '^[[8$' '^[[1;2F' # shift+end + + # Tmux sends different key codes. Translate them to xterm equivalents. + bindkey -M $keymap -s '^[[1~' '^[[H' # home + bindkey -M $keymap -s '^[[4~' '^[[F' # end + bindkey -M $keymap -s '^[^[[A' '^[[1;3A' # alt+up + bindkey -M $keymap -s '^[^[[B' '^[[1;3B' # alt+down + bindkey -M $keymap -s '^[^[[D' '^[[1;3D' # alt+left + bindkey -M $keymap -s '^[^[[C' '^[[1;3C' # alt+right + bindkey -M $keymap -s '^[^[[1~' '^[[1;3H' # alt+home + bindkey -M $keymap -s '^[^[[4~' '^[[1;3F' # alt+end + bindkey -M $keymap -s '^[^[[3~' '^[[3;3~' # alt+delete + + # iTerm2 sends different key codes. Translate them to xterm equivalents. + # Missing (depending on settings): ctrl+{up,down,left,right}, {ctrl,alt}+{delete,backspace}. + bindkey -M $keymap -s '^[^[[A' '^[[1;3A' # alt+up + bindkey -M $keymap -s '^[^[[B' '^[[1;3B' # alt+down + bindkey -M $keymap -s '^[^[[D' '^[[1;3D' # alt+left + bindkey -M $keymap -s '^[^[[C' '^[[1;3C' # alt+right + bindkey -M $keymap -s '^[[1;9A' '^[[1;3A' # alt+up + bindkey -M $keymap -s '^[[1;9B' '^[[1;3B' # alt+down + bindkey -M $keymap -s '^[[1;9D' '^[[1;3D' # alt+left + bindkey -M $keymap -s '^[[1;9C' '^[[1;3C' # alt+right + bindkey -M $keymap -s '^[[1;9H' '^[[1;3H' # alt+home + bindkey -M $keymap -s '^[[1;9F' '^[[1;3F' # alt+end + + # TODO: Add missing translations. +done + +# Move cursor one char backward. +bindkey '^[[D' backward-char # left +# Move cursor one char forward. +bindkey '^[[C' forward-char # right +if (( _z4h_use[zsh-history-substring-search] )); then + # Move cursor one line up or fetch the previous command from LOCAL history. + bindkey '^P' z4h-up-substring-local # ctrl+p + bindkey '^[[A' z4h-up-substring-local # up + # Move cursor one line down or fetch the next command from LOCAL history. + bindkey '^N' z4h-down-substring-local # ctrl+n + bindkey '^[[B' z4h-down-substring-local # down +else + # Move cursor one line up or fetch the previous command from LOCAL history. + bindkey '^P' z4h-up-prefix-local # ctrl+p + bindkey '^[[A' z4h-up-prefix-local # up + # Move cursor one line down or fetch the next command from LOCAL history. + bindkey '^N' z4h-down-prefix-local # ctrl+n + bindkey '^[[B' z4h-down-prefix-local # down +fi + +# Move cursor one line up or fetch the previous command from GLOBAL history. +bindkey '^[[1;5A' z4h-up-prefix-global # ctrl+up +# Move cursor one line down or fetch the next command from GLOBAL history. +bindkey '^[[1;5B' z4h-down-prefix-global # ctrl+down +# Move cursor to the beginning of line. +bindkey '^[[H' beginning-of-line # home +# Move cursor to the end of line. +bindkey '^[[F' end-of-line # end +# Move cursor to the beginning of buffer. +bindkey '^[[1;5H' z4h-beginning-of-buffer # ctrl+home +bindkey '^[[1;3H' z4h-beginning-of-buffer # alt+home +# Move cursor to the end of buffer. +bindkey '^[[1;5F' z4h-end-of-buffer # ctrl+end +bindkey '^[[1;3F' z4h-end-of-buffer # alt+end +# Delete the character under the cursor. +bindkey '^D' delete-char # ctrl+d +bindkey '^[[3~' delete-char # delete +# Delete next word. +bindkey '^[d' z4h-kill-word # alt+d +bindkey '^[D' z4h-kill-word # alt+D +bindkey '^[[3;5~' z4h-kill-word # ctrl+del +bindkey '^[[3;3~' z4h-kill-word # alt+del +# Delete previous word. +bindkey '^W' z4h-backward-kill-word # ctrl+w +bindkey '^[^?' z4h-backward-kill-word # alt+bs +bindkey '^[^H' z4h-backward-kill-word # ctrl+alt+bs + +# Move cursor one zsh word forward. +bindkey '^[[1;6C' z4h-forward-zword # ctrl+shift+right +# Move cursor one zsh word backward. +bindkey '^[[1;6D' z4h-backward-zword # ctrl+shift+left +# Delete next zsh word. +bindkey '^[[3;6~' z4h-kill-zword # ctrl+shift+del + +# Delete line before cursor. +bindkey '^[k' backward-kill-line # alt+k +bindkey '^[K' backward-kill-line # alt+K +# Delete all lines. +bindkey '^[j' kill-buffer # alt+j +bindkey '^[J' kill-buffer # alt+J +# Push buffer to ephemeral history (won't be saved to HISTFILE) and delete all lines. +bindkey '^[o' z4h-stash-buffer # alt+o +bindkey '^[O' z4h-stash-buffer # alt+O +# Accept autosuggestion. +bindkey '^[m' z4h-autosuggest-accept # alt+m +bindkey '^[M' z4h-autosuggest-accept # alt+M +# Undo and redo. +bindkey '^[[Z' undo # shift+tab +bindkey '^[/' redo # alt+/ +# Expand alias/glob/parameter. +bindkey '^ ' z4h-expand # ctrl+space +# Generic command completion. +bindkey '^I' z4h-fzf-complete # tab +if (( _z4h_use[fzf] )); then + # Command history. + bindkey '^R' z4h-fzf-history # ctrl+r +fi +# Show help for the command at cursor. +bindkey '^[h' run-help # alt+h +bindkey '^[H' run-help # alt+H +# Do nothing (better than printing '~'). +bindkey '^[[5~' z4h-do-nothing # pageup +bindkey '^[[6~' z4h-do-nothing # pagedown + +# Move cursor one word backward. +bindkey '^[b' z4h-backward-word # alt+b +bindkey '^[B' z4h-backward-word # alt+B +bindkey '^[[1;3D' z4h-backward-word # alt+left +bindkey '^[[1;5D' z4h-backward-word # ctrl+left + +# Move cursor one word forward. +bindkey '^[f' z4h-forward-word # alt+f +bindkey '^[F' z4h-forward-word # alt+F +bindkey '^[[1;3C' z4h-forward-word # alt+right +bindkey '^[[1;5C' z4h-forward-word # ctrl+right + +# cd into the previous directory. +bindkey '^[[1;2D' z4h-cd-back # shift+left +# cd into the next directory. +bindkey '^[[1;2C' z4h-cd-forward # shift+right +# cd into the parent directory. +bindkey '^[[1;2A' z4h-cd-up # shift+up +if (( _z4h_use[fzf] )); then + # cd into a subdirectory (interactive). + bindkey '^[[1;2B' z4h-cd-down # shift+down +fi + +# Directory history. +bindkey '^[r' z4h-fzf-dir-history # alt+r +bindkey '^[R' z4h-fzf-dir-history # alt+R + +if (( _z4h_can_save_restore_screen )) && + zstyle -T :z4h: prompt-at-bottom; then + bindkey '^L' z4h-clear-screen-soft-bottom # ctrl+l + bindkey '^[^L' z4h-clear-screen-hard-bottom # ctrl+alt+l +else + bindkey '^L' z4h-clear-screen-soft-top # ctrl+l + bindkey '^[^L' z4h-clear-screen-hard-top # ctrl+alt+l +fi + +if zstyle -T :z4h:bindkey macos-option-as-alt; then + typeset -grA _z4h_macos_opt_key=( + q 'œ' + w '∑' + r '®' + t '†' + o 'ø' + p 'π' + [ '“' + ] '‘' + a 'å' + s 'ß' + d '∂' + f 'ƒ' + g '©' + h '˙' + j '∆' + k '˚' + l '¬' + z 'Ω' + x '≈' + c 'ç' + v '√' + b '∫' + m 'µ' + , '≤' + . '≥' + / '÷' + \\ '«' + # Option+y is just '\'. + # Dead key functionality: e, u, i, n. + Q 'Œ' + W '„' + E '´' + R '‰' + T 'ˇ' + Y 'Á' + U '¨' + I 'ˆ' + O 'Ø' + P '∏' + A 'Å' + S 'Í' + D 'Î' + F 'Ï' + G '˝' + H 'Ó' + J 'Ô' + K '\357\243\277' + L 'Ò' + Z '¸' + X '˛' + C 'Ç' + V '◊' + B 'ı' + N '˜' + M 'Â' + ) + + bindkey '∂' z4h-kill-word # opt+d + bindkey 'Î' z4h-kill-word # opt+D + bindkey '˚' backward-kill-line # opt+k + bindkey '\357\243\277' backward-kill-line # opt+K (U+F8FF) + bindkey '∆' kill-buffer # opt+j + bindkey 'Ô' kill-buffer # opt+J + bindkey 'ø' z4h-stash-buffer # opt+o + bindkey 'Ø' z4h-stash-buffer # opt+O + bindkey 'µ' z4h-autosuggest-accept # opt+m + bindkey 'Â' z4h-autosuggest-accept # opt+M + bindkey '÷' redo # opt+/ + bindkey '˙' run-help # opt+h + bindkey 'Ó' run-help # opt+H + bindkey '∫' z4h-backward-word # opt+b + bindkey 'ı' z4h-backward-word # opt+B + bindkey '®' z4h-fzf-dir-history # opt+r + bindkey '‰' z4h-fzf-dir-history # opt+R +else + typeset -grA _z4h_macos_opt_key=() +fi + +if zstyle -t :z4h:bindkey keyboard mac; then + local del=Fn+Delete +else + local del=Delete +fi + +typeset -gA _z4h_key=( + Tab '^I' + Space ' ' + Enter '^M' + Escape '^[' + Ctrl+/ '^_' + Ctrl+_ '^_' + Ctrl+Space '^ ' + Alt+Space '^[ ' + Shift+Tab '^[[Z' + + Up '^[[A' + Down '^[[B' + Right '^[[C' + Left '^[[D' + Home '^[[H' + End '^[[F' + Insert '^[[2~' + $del '^[[3~' + PageUp '^[[5~' + PageDown '^[[6~' + Backspace '^?' + + Shift+Up '^[[1;2A' + Shift+Down '^[[1;2B' + Shift+Right '^[[1;2C' + Shift+Left '^[[1;2D' + Shift+Home '^[[1;2H' + Shift+End '^[[1;2F' + Shift+Insert '^[[2;2~' + Shift+$del '^[[3;2~' + Shift+PageUp '^[[5;2~' + Shift+PageDown '^[[6;2~' + Shift+Backspace '^?' + + Alt+Up '^[[1;3A' + Alt+Down '^[[1;3B' + Alt+Right '^[[1;3C' + Alt+Left '^[[1;3D' + Alt+Home '^[[1;3H' + Alt+End '^[[1;3F' + Alt+Insert '^[[2;3~' + Alt+$del '^[[3;3~' + Alt+PageUp '^[[5;3~' + Alt+PageDown '^[[6;3~' + Alt+Backspace '^[^?' + + Alt+Shift+Up '^[[1;4A' + Alt+Shift+Down '^[[1;4B' + Alt+Shift+Right '^[[1;4C' + Alt+Shift+Left '^[[1;4D' + Alt+Shift+Home '^[[1;4H' + Alt+Shift+End '^[[1;4F' + Alt+Shift+Insert '^[[2;4~' + Alt+Shift+$del '^[[3;4~' + Alt+Shift+PageUp '^[[5;4~' + Alt+Shift+PageDown '^[[6;4~' + Alt+Shift+Backspace '^[^H' + + Ctrl+Up '^[[1;5A' + Ctrl+Down '^[[1;5B' + Ctrl+Right '^[[1;5C' + Ctrl+Left '^[[1;5D' + Ctrl+Home '^[[1;5H' + Ctrl+End '^[[1;5F' + Ctrl+Insert '^[[2;5~' + Ctrl+$del '^[[3;5~' + Ctrl+PageUp '^[[5;5~' + Ctrl+PageDown '^[[6;5~' + Ctrl+Backspace '^H' + + Ctrl+Shift+Up '^[[1;6A' + Ctrl+Shift+Down '^[[1;6B' + Ctrl+Shift+Right '^[[1;6C' + Ctrl+Shift+Left '^[[1;6D' + Ctrl+Shift+Home '^[[1;6H' + Ctrl+Shift+End '^[[1;6F' + Ctrl+Shift+Insert '^[[2;6~' + Ctrl+Shift+$del '^[[3;6~' + Ctrl+Shift+PageUp '^[[5;6~' + Ctrl+Shift+PageDown '^[[6;6~' + Ctrl+Shift+Backspace '^?' + + Ctrl+Alt+Up '^[[1;7A' + Ctrl+Alt+Down '^[[1;7B' + Ctrl+Alt+Right '^[[1;7C' + Ctrl+Alt+Left '^[[1;7D' + Ctrl+Alt+Home '^[[1;7H' + Ctrl+Alt+End '^[[1;7F' + Ctrl+Alt+Insert '^[[2;7~' + Ctrl+Alt+$del '^[[3;7~' + Ctrl+Alt+PageUp '^[[5;7~' + Ctrl+Alt+PageDown '^[[6;7~' + Ctrl+Alt+Backspace '^[^H' + + Ctrl+Alt+Shift+Up '^[[1;8A' + Ctrl+Alt+Shift+Down '^[[1;8B' + Ctrl+Alt+Shift+Right '^[[1;8C' + Ctrl+Alt+Shift+Left '^[[1;8D' + Ctrl+Alt+Shift+Home '^[[1;8H' + Ctrl+Alt+Shift+End '^[[1;8F' + Ctrl+Alt+Shift+Insert '^[[2;8~' + Ctrl+Alt+Shift+$del '^[[3;8~' + Ctrl+Alt+Shift+PageUp '^[[5;8~' + Ctrl+Alt+Shift+PageDown '^[[6;8~' + Ctrl+Alt+Shift+Backspace '^?' + + # Duplicate all Alt bindings with s/Alt/Option/. + Option+Space '^[ ' + + Option+Up '^[[1;3A' + Option+Down '^[[1;3B' + Option+Right '^[[1;3C' + Option+Left '^[[1;3D' + Option+Home '^[[1;3H' + Option+End '^[[1;3F' + Option+Insert '^[[2;3~' + Option+$del '^[[3;3~' + Option+PageUp '^[[5;3~' + Option+PageDown '^[[6;3~' + Option+Backspace '^[^?' + + Option+Shift+Up '^[[1;4A' + Option+Shift+Down '^[[1;4B' + Option+Shift+Right '^[[1;4C' + Option+Shift+Left '^[[1;4D' + Option+Shift+Home '^[[1;4H' + Option+Shift+End '^[[1;4F' + Option+Shift+Insert '^[[2;4~' + Option+Shift+$del '^[[3;4~' + Option+Shift+PageUp '^[[5;4~' + Option+Shift+PageDown '^[[6;4~' + Option+Shift+Backspace '^[^H' + + Ctrl+Option+Up '^[[1;7A' + Ctrl+Option+Down '^[[1;7B' + Ctrl+Option+Right '^[[1;7C' + Ctrl+Option+Left '^[[1;7D' + Ctrl+Option+Home '^[[1;7H' + Ctrl+Option+End '^[[1;7F' + Ctrl+Option+Insert '^[[2;7~' + Ctrl+Option+$del '^[[3;7~' + Ctrl+Option+PageUp '^[[5;7~' + Ctrl+Option+PageDown '^[[6;7~' + Ctrl+Option+Backspace '^[^H' + + Ctrl+Option+Shift+Up '^[[1;8A' + Ctrl+Option+Shift+Down '^[[1;8B' + Ctrl+Option+Shift+Right '^[[1;8C' + Ctrl+Option+Shift+Left '^[[1;8D' + Ctrl+Option+Shift+Home '^[[1;8H' + Ctrl+Option+Shift+End '^[[1;8F' + Ctrl+Option+Shift+Insert '^[[2;8~' + Ctrl+Option+Shift+$del '^[[3;8~' + Ctrl+Option+Shift+PageUp '^[[5;8~' + Ctrl+Option+Shift+PageDown '^[[6;8~' + Ctrl+Option+Shift+Backspace '^?' +) + +if zstyle -t :z4h:bindkey keyboard mac; then + # Duplicate all Backspace bindings with s/Backspace/Delete/. + _z4h_key+=( + Delete '^?' + Shift+Delete '^?' + Alt+Delete '^[^?' + Alt+Shift+Delete '^[^H' + Ctrl+Delete '^H' + Ctrl+Shift+Delete '^?' + Ctrl+Alt+Delete '^[^H' + Ctrl+Alt+Shift+Delete '^?' + Option+Delete '^[^?' + Option+Shift+Delete '^[^H' + Ctrl+Option+Delete '^[^H' + Ctrl+Option+Shift+Delete '^?' + ) +fi + +typeset -grA _z4h_key + +# Configure completions. +zstyle ':completion:*' matcher-list "m:{a-z}={A-Z}" +zstyle ':completion:*' menu "false" +zstyle ':completion:*' verbose "true" +zstyle ':completion:::::' insert-tab "pending" +zstyle ':completion:*:-subscript-:*' tag-order "indexes parameters" +zstyle ':completion:*:-tilde-:*' tag-order "directory-stack" "named-directories" "users" +zstyle ':completion:*' squeeze-slashes "true" +zstyle ':completion:*:rm:*' ignore-line "other" +zstyle ':completion:*:kill:*' ignore-line "other" +zstyle ':completion:*:diff:*' ignore-line "other" +zstyle ':completion:*:rm:*' file-patterns "*:all-files" +zstyle ':completion:*:paths' accept-exact-dirs "true" +zstyle ':completion:*' single-ignored "show" +zstyle ':completion:*:functions' ignored-patterns "-*|_*" +zstyle ':completion:*:parameters' ignored-patterns \ + "_(z4h|p9k|_p9k|POWERLEVEL9K|gitstatus|GITSTATUS|zsh_highlight|zsh_autosuggest|ZSH_HIGHLIGHT|ZSH_AUTOSUGGEST)*" + +if (( ! _z4h_dangerous_root )); then + zstyle ':completion:*' use-cache "true" + zstyle ':completion:*' cache-path "$Z4H/cache/zcompcache-$ZSH_VERSION" +fi + +zstyle ':completion:*:ssh:argument-1:*' sort 'true' +zstyle ':completion:*:scp:argument-rest:*' sort 'true' + +zstyle ':completion:*:git-*:argument-rest:heads' ignored-patterns '(FETCH_|ORIG_|*/|)HEAD' +zstyle ':completion:*:git-*:argument-rest:heads-local' ignored-patterns '(FETCH_|ORIG_|)HEAD' +zstyle ':completion:*:git-*:argument-rest:heads-remote' ignored-patterns '*/HEAD' +zstyle ':completion:*:git-*:argument-rest:commits' ignored-patterns '*' +zstyle ':completion:*:git-*:argument-rest:commit-objects' ignored-patterns '*' +zstyle ':completion:*:git-*:argument-rest:recent-branches' ignored-patterns '*' + +# Make it possible to use completion specifications and functions written for bash. +autoload -Uz bashcompinit +bashcompinit + +if (( _z4h_use[powerlevel10k] )); then + # Initialize prompt. Type `p10k configure` or edit $POWERLEVEL9K_CONFIG_FILE to customize it. + () { + local XDG_CACHE_HOME=$Z4H/cache/powerlevel10k + builtin source $Z4H/powerlevel10k/powerlevel10k.zsh-theme + } + z4h source -c $POWERLEVEL9K_CONFIG_FILE +fi + +if [[ -v _z4h_iterm_cmd ]]; then + -z4h-iterm2-dump + -z4h-tmux-bypass '\e]1337;ShellIntegrationVersion=12;shell=zsh\a' +fi +-z4h-set-term-title-precmd || return + +if (( _z4h_use[zsh-autosuggestions] )); then + if (( terminfo[colors] >= 256 )); then + LS_COLORS+=':no=38;5;248' + ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=244' # the default is hard to see + else + LS_COLORS+=':no=1;30' + ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE='fg=black,bold' # the default is outside of 8-color range + fi + + ZSH_AUTOSUGGEST_MANUAL_REBIND=1 + + # Tell zsh-autosuggestions how to handle different widgets. + typeset -g ZSH_AUTOSUGGEST_EXECUTE_WIDGETS=() + typeset -g ZSH_AUTOSUGGEST_CLEAR_WIDGETS=( + z4h-fzf-history + z4h-down-prefix-global + z4h-down-prefix-local + z4h-up-prefix-global + z4h-up-prefix-local + ) + if (( _z4h_use[zsh-history-substring-search] )); then + ZSH_AUTOSUGGEST_CLEAR_WIDGETS+=( + z4h-down-substring-global + z4h-down-substring-local + z4h-up-substring-global + z4h-up-substring-local + ) + fi + typeset -g ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS=( + emacs-forward-word + forward-word + vi-find-next-char + vi-find-next-char-skip + vi-forward-blank-word + vi-forward-blank-word-end + vi-forward-word + vi-forward-word-end + z4h-forward-word + z4h-forward-zword + ) + typeset -g ZSH_AUTOSUGGEST_ACCEPT_WIDGETS=( + z4h-end-of-buffer + ) + + if zstyle -T :z4h:autosuggestions forward-char accept; then + ZSH_AUTOSUGGEST_ACCEPT_WIDGETS+=(forward-char vi-forward-char) + else + ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS+=(forward-char vi-forward-char) + fi + + if zstyle -T :z4h:autosuggestions end-of-line accept; then + ZSH_AUTOSUGGEST_ACCEPT_WIDGETS+=(end-of-line vi-add-eol vi-end-of-line) + else + ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS+=(end-of-line vi-add-eol vi-end-of-line) + fi + + builtin source $Z4H/zsh-autosuggestions/zsh-autosuggestions.zsh + precmd_functions=(${precmd_functions:#_zsh_autosuggest_start}) + + _zsh_autosuggest_widget_execute() { + -z4h-autosuggest-fetch + if [[ -n $POSTDISPLAY ]]; then + BUFFER=$BUFFER$POSTDISPLAY + region_highlight[-1]=() + typeset -g _z4h_autosuggest_buffer=$BUFFER + unset _z4h_autosuggestion POSTDISPLAY + fi + zle .accept-line + } + + _zsh_autosuggest_widget_clear() { + _zsh_autosuggest_invoke_original_widget "$@" + local -i ret=$? + if [[ $CONTEXT == start ]]; then + [[ -z $POSTDISPLAY ]] || region_highlight[-1]=() + if (( ret )); then + -z4h-autosuggest-fetch + else + typeset -g _z4h_autosuggest_buffer=$BUFFER + unset _z4h_autosuggestion POSTDISPLAY + fi + fi + return ret + } + + _zsh_autosuggest_widget_accept() { + -z4h-autosuggest-fetch + if (( ${#POSTDISPLAY} == 0 || CURSOR < _z4h_cursor_max() )); then + _zsh_autosuggest_invoke_original_widget "$@" + return + fi + BUFFER="$BUFFER$POSTDISPLAY" + region_highlight[-1]=() + unset _z4h_autosuggestion POSTDISPLAY + _zsh_autosuggest_invoke_original_widget "$@" + local -i ret=$? + typeset -g _z4h_autosuggest_buffer="$BUFFER" + typeset -gi CURSOR='_z4h_cursor_max()' + return ret + } + + _zsh_autosuggest_widget_partial_accept() { + -z4h-autosuggest-fetch + if (( ${#POSTDISPLAY} == 0 || CURSOR < _z4h_cursor_max() )); then + _zsh_autosuggest_invoke_original_widget "$@" + return + fi + local -i buf_len=${#BUFFER} + BUFFER="$BUFFER$POSTDISPLAY" + _zsh_autosuggest_invoke_original_widget "$@" + local -i ret=$? + local -i cursor=CURSOR + if [[ $KEYMAP == vicmd && -n ${BUFFER##*$'\n'} ]]; then + (( ++cursor )) + fi + if (( cursor > buf_len )); then + BUFFER=${BUFFER:0:$cursor} + POSTDISPLAY=${POSTDISPLAY:$((cursor - buf_len))} + else + BUFFER=${BUFFER:0:$buf_len} + fi + typeset -g _z4h_autosuggest_buffer=$BUFFER + return ret + } + + _zsh_autosuggest_widget_modify() { + _zsh_autosuggest_invoke_original_widget "$@" + local -i retval=$? + [[ -z $POSTDISPLAY ]] || region_highlight[-1]=() + -z4h-autosuggest-fetch + return retval + } + + _zsh_autosuggest_widget_fetch() { + [[ -z $POSTDISPLAY ]] || region_highlight[-1]=() + -z4h-autosuggest-fetch + } + + _zsh_autosuggest_widget_suggest() { + [[ -z $BUFFER || $CONTEXT != start ]] && return + [[ -z $POSTDISPLAY ]] || region_highlight[-1]=() + POSTDISPLAY=${1-} + typeset -g _z4h_autosuggest_buffer="$BUFFER" + typeset -g _z4h_autosuggestion="${BUFFER}${POSTDISPLAY}" + if [[ -n $POSTDISPLAY ]]; then + region_highlight+=( + "${#BUFFER} $((${#BUFFER} + ${#POSTDISPLAY})) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE") + fi + } + + _zsh_autosuggest_widget_enable() { + (( ${+_ZSH_AUTOSUGGEST_DISABLED} )) || return 0 + unset _ZSH_AUTOSUGGEST_DISABLED + _zsh_autosuggest_widget_fetch + } + + _zsh_autosuggest_widget_disable() { + (( ${+_ZSH_AUTOSUGGEST_DISABLED} )) && return + typeset -g _ZSH_AUTOSUGGEST_DISABLED + [[ -z $POSTDISPLAY ]] || region_highlight[-1]=() + unset POSTDISPLAY _z4h_autosuggest_buffer _z4h_autosuggestion + } + + _zsh_autosuggest_widget_toggle() { + if (( ${+_ZSH_AUTOSUGGEST_DISABLED} )); then + _zsh_autosuggest_widget_enable + else + _zsh_autosuggest_widget_disable + fi + } +else + function -z4h-autosuggest-fetch() { + unset POSTDISPLAY _z4h_autosuggest_buffer _z4h_autosuggestion + } +fi + +# Define _zsh_highlight before sourcing history-substring-search to work around bugs +# in the latter: https://github.com/romkatv/zsh4humans/issues/58. +function _zsh_highlight() { + if [[ $WIDGET == zle-line-finish || ! -v _z4h_substring_search_highlight ]]; then + region_highlight=() + else + region_highlight=("${_z4h_substring_search_highlight[@]}") + fi +} + +if (( _z4h_use[zsh-history-substring-search] )); then + builtin source $Z4H/zsh-history-substring-search/zsh-history-substring-search.zsh +fi + +if (( _z4h_use[zsh-syntax-highlighting] )); then + if (( terminfo[colors] >= 256 )); then + typeset -gA ZSH_HIGHLIGHT_STYLES=(comment fg=96) # the default is hard to see + fi + ZSH_HIGHLIGHT_MAXLENGTH=1024 # don't colorize long command lines (slow) + ZSH_HIGHLIGHT_HIGHLIGHTERS=(main brackets) # main syntax highlighting plus matching brackets + typeset -gi zsh_highlight__memo_feature=0 +fi + +local event +for event in pre-redraw init finish; do + if (( $+widgets[zle-line-$event] )); then + zle -A -- zle-line-$event -z4h-orig-zle-line-$event + fi + zle -N -- zle-line-$event -z4h-zle-line-$event +done + +function -z4h-post-init() { + eval "$_z4h_opt" + + precmd_functions=(${precmd_functions:#-z4h-post-init}) + + if zstyle -t :z4h:direnv enable; then + if [[ -v functions[_direnv_hook] ]]; then + unfunction _direnv_hook + chpwd_functions=(${chpwd_functions:#_direnv_hook}) + precmd_functions=(${precmd_functions:#_direnv_hook}) + fi + [[ -v _z4h_direnv_initialized ]] || -z4h-direnv-init 1 + fi + if [[ -v _z4h_direnv_initialized ]]; then + precmd_functions=(-z4h-direnv-hook $precmd_functions) + chpwd_functions=(-z4h-direnv-hook $chpwd_functions) + unset _z4h_direnv_initialized + fi + + typeset -gi _z4h_compinit_fd + if [[ -v __p9k_fd_0 ]] && zselect -t0 -r $__p9k_fd_0 && + sysopen -o cloexec -ru _z4h_compinit_fd /dev/null; then + zle -F $_z4h_compinit_fd -z4h-compinit + else + unset _z4h_compinit_fd + -z4h-compinit + -z4h-update-dir-history + fi + + if (( _z4h_use[zsh-syntax-highlighting] )); then + () { + local -hA widgets=( + zle-line-finish user:_zsh_highlight_widget_zle-line-finish + zle-isearch-update user:_zsh_highlight_widget_zle-isearch-update + ) + builtin source $Z4H/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh + } + preexec_functions=(${preexec_functions:#_zsh_highlight_preexec_hook}) + eval '_zsh_highlight() { + [[ ${WIDGET-} == zle-line-pre-redraw && ${LASTWIDGET-} == accept-line ]] && return + { + if [[ ${#BUFFER} -gt ${ZSH_HIGHLIGHT_MAXLENGTH:-${#BUFFER}} ]]; then + region_highlight=() + else + local -ih PENDING=0 KEYS_QUEUED_COUNT=0 + '$functions[_zsh_highlight]' + fi + } always { + [[ $WIDGET == zle-line-finish ]] || region_highlight+=("${_z4h_substring_search_highlight[@]}") + } + }' + fi + + if (( _z4h_use[zsh-history-substring-search] )); then + function _history-substring-search-end() { + builtin eval "$_z4h_opt" + if (( _history_substring_search_refresh_display )); then + typeset -gi CURSOR='_z4h_cursor_max()' + fi + typeset -g _history_substring_search_result=$BUFFER + typeset -gi _history_substring_search_cursor=CURSOR + unset _z4h_substring_search_highlight + [[ -z $_history_substring_search_query_highlight ]] && return + local query_part escaped_query_part REPLY + local -i highlight_start_index highlight_end_index query_part_match_index + for query_part in $_history_substring_search_query_parts; do + escaped_query_part=${query_part//(#m)[\][()|\\*?#<>~^]/\\$MATCH} + query_part_match_index=${${BUFFER:$highlight_start_index}[(i)(#$HISTORY_SUBSTRING_SEARCH_GLOBBING_FLAGS)${escaped_query_part}]} + (( query_part_match_index > $#BUFFER - highlight_start_index )) && continue + highlight_start_index=$(( highlight_start_index + query_part_match_index )) + highlight_end_index=$(( highlight_start_index + ${#query_part} )) + _z4h_substring_search_highlight+=( + "$((highlight_start_index - 1)) $((highlight_end_index - 1)) $_history_substring_search_query_highlight") + done + } + + # This function is buggy in the original implementation when vicmd is active. + _history-substring-search-up-buffer() { + builtin eval "$_z4h_opt" + + # The original implementation is equivalent to this one with the exception + # of the condition on the next line being (( CURSOR != $#BUFFER )). + (( CURSOR != _z4h_cursor_max() )) || return + + [[ $LBUFFER == *$'\n'* ]] || return + builtin zle up-line-or-history || true + } + + # See _history-substring-search-up-buffer above. + _history-substring-search-down-buffer() { + builtin eval "$_z4h_opt" + (( CURSOR != _z4h_cursor_max() )) || return + [[ $RBUFFER == *$'\n'* ]] || return + builtin zle down-line-or-history || true + } + elif (( $+functions[_history-substring-search-end] )); then + # Turn off highlighting in _history-substring-search-end. It doesn't work anyway. + function _history-substring-search-end() { + emulate -L zsh + _history_substring_search_result=$BUFFER + if (( _history_substring_search_refresh_display )); then + typeset -gi CURSOR='_z4h_cursor_max()' + fi + } + fi + + if (( _z4h_use[zsh-autosuggestions] )); then + local suggest_special=( + $ZSH_AUTOSUGGEST_EXECUTE_WIDGETS + $ZSH_AUTOSUGGEST_CLEAR_WIDGETS + $ZSH_AUTOSUGGEST_PARTIAL_ACCEPT_WIDGETS + $ZSH_AUTOSUGGEST_ACCEPT_WIDGETS) + typeset -g ZSH_AUTOSUGGEST_IGNORE_WIDGETS=(${${(k)widgets}:|suggest_special}) + unset ZSH_AUTOSUGGEST_USE_ASYNC + _zsh_autosuggest_start + fi + + if [[ -v functions[bracketed-paste-magic] ]]; then + builtin unfunction bracketed-paste-magic + builtin autoload -Uz -- $Z4H/zsh4humans/fn/bracketed-paste-magic + fi + + if (( ! $+_z4h_iterm_cmd )) && [[ $ITERM_SHELL_INTEGRATION_INSTALLED == Yes ]]; then + -z4h-error-iterm2-integration + fi + + if [[ -v _z4h_tty_fd ]]; then + if [[ -v function[__vte_osc7] ]]; then + builtin functions -c -- -z4h-vte-osc7 __vte_osc7 + fi + if { zstyle -t :z4h: term-shell-integration || zstyle -t :z4h: iterm2-integration } && + [[ $TERM != (dumb|linux) ]]; then + builtin functions -c -- -z4h-vte-osc7 __vte_osc7 + if (( ! $precmd_functions[(I)__vte_osc7] )); then + precmd_functions=(__vte_osc7 $precmd_functions) + __vte_osc7 + fi + if [[ -v z4h_win_env && -z ${SSH_CONNECTION-} && ${P9K_SSH-} != 1 ]]; then + chpwd_functions+=(-z4h-osc9) + -z4h-osc9 + fi + fi + fi + + precmd_functions=(-z4h-wrap-commands $precmd_functions) + + # Send WINCH just in case we've set incorrect TTY dimensions manually. + builtin kill -WINCH $$ +} + +precmd_functions=(-z4h-post-init $precmd_functions) +[[ -e $Z4H/welcome ]] && precmd_functions+=(-z4h-welcome) + +typeset -g iterm2_hostname=${HOST:-${(%):-%m}} + +typeset -ga _z4h_dir_history + +function compdef() { + eval "$_z4h_opt" + _z4h_compdef+=("${(pj:\0:)@}") +} + +compdef _ssh ssh +compdef _sudo sudo + +typeset -g VIRTUAL_ENV_DISABLE_PROMPT=1 + +PS2= +RPS2='%F{3}%^%f' diff --git a/fn/-z4h-insert-all b/fn/-z4h-insert-all new file mode 100644 index 0000000..95e378b --- /dev/null +++ b/fn/-z4h-insert-all @@ -0,0 +1,19 @@ +#!/usr/bin/env zsh + +local hook=${widgets[zle-line-pre-redraw]-} +if [[ $hook == user:* ]]; then + hook=${hook#user:} +else + hook= +fi + +{ + if [[ -n $hook ]]; then + builtin zle -D zle-line-pre-redraw + fi + builtin zle -- -z4h-comp-insert-all +} always { + if [[ -n $hook ]]; then + zle -N -- zle-line-pre-redraw "$hook" + fi +} diff --git a/fn/-z4h-install-many b/fn/-z4h-install-many new file mode 100644 index 0000000..32de43a --- /dev/null +++ b/fn/-z4h-install-many @@ -0,0 +1,125 @@ +#!/usr/bin/env zsh + +local success + +{ + if (( ! $+_z4h_no_autoupdate && ! $+Z4H_SSH && !_z4h_dangerous_root && _z4h_zle )) && + zstyle -t :z4h: auto-update ask && [[ -t 0 && -t 1 && -t 2 ]]; then + typeset -gri _z4h_no_autoupdate=1 + local days + if zstyle -s :z4h: auto-update-days days && [[ $days == <-> ]]; then + # Check if update is required. + local -a last_update_ts + if zstat -A last_update_ts +mtime -- $Z4H/cache/last-update-ts 2>/dev/null && + (( EPOCHSECONDS - last_update_ts[1] >= 86400 * days )) && + [[ ! -e $Z4H/.updating ]]; then + local REPLY + { + read -q ${(%):-"?%F{3}z4h%f: update dependencies? [y/N]: "} && REPLY=y + } always { + print -u ${_z4h_tty_fd-2} + } + print -n >$Z4H/cache/last-update-ts || return + if [[ $REPLY == y ]]; then + z4h update && return + fi + print -Pru2 -- "%F{3}z4h%f: won't ask to update again for %B$days%b day(s)" + print -Pru2 -- "" + print -Pru2 -- "To update manually, type:" + print -Pru2 -- "" + print -Pru2 -- " %F{2}z4h%f %Bupdate%b" + print -Pru2 -- "" + fi + fi + fi + + local -aU want=(${_z4h_install_queue%%@*}) + local have=($Z4H/$^want(N)) + have=(${have#$Z4H/}) + local missing=(${want:|have}) + (( $#missing )) || return 0 + + if [[ ! -v commands[uname] ]]; then + print -Pru2 -- '%F{3}z4h%f: command not found: %F{1}uname%f' + return 1 + fi + + local uname_sm + uname_sm=$(uname -sm) || return + case ${(L)uname_sm} in + 'darwin arm64');; + 'darwin x86_64');; + 'linux aarch64');; + 'linux armv6l');; + 'linux armv7l');; + 'linux armv8l');; + 'linux x86_64');; + 'linux i686');; + *) + print -Pru2 -- "%F{3}z4h%f: unsupported platform: %F{1}${uname_sm//\%/%%}%f" + return 1 + ;; + esac + + local repo base queue=() + for repo in $_z4h_install_queue; do + base=${repo%%@*} + (( missing[(Ie)$base] )) || continue + missing=(${missing:#$base}) + queue+=($repo) + done + + if (( _z4h_dangerous_root )); then + for repo in $queue; do + print -Pru2 -- "%F{3}z4h%f: refusing to %Binstall%b as %F{1}root%f: %B${repo//\%/%%}%b" + done + return 1 + fi + typeset -gi _z4h_installed_something=1 + + if (( $#queue == 1 )) || [[ -o interactive && ZSH_SUBSHELL -ne 0 ]] || + ! () { setopt localoptions monitor 2>/dev/null }; then + for repo in $queue; do + -z4h-install-one $repo || return + done + return + fi + + if (( $+commands[mktemp] )); then + success="$(command mktemp -- $Z4H/tmp/install-many.XXXXXXXXXX)" || return + else + success=$Z4H/tmp/install-many.$$ + zf_rm -rf -- $success || return + print -n >$success || return + fi + + function -z4h-install-many-parallel() { + eval "$_z4h_opt" + local repo fd head=() tail=() + local omz=ohmyzsh/ohmyzsh + queue=(${(M)queue:#powerlevel10k} ${queue:#powerlevel10k}) + queue=(${queue:#$omz} ${(M)queue:#$omz}) + for repo in $queue; do + if (( $#head + $#tail == 8 )); then + fd=$head[1] + <&$fd >&2 || return + exec {fd}<&- || return + shift 1 head + fi + exec {fd}< <( (-z4h-install-one $repo 2>&1 ) || zf_rm -f -- $success ) || return + [[ $repo == powerlevel10k ]] && tail+=$fd || head+=$fd + done + for fd in $head $tail; do + <&$fd >&2 || return + exec {fd}<&- || return + done + } + + -z4h-run-process-tree -z4h-install-many-parallel || return + [[ -e $success ]] || return + +} always { + unset _z4h_install_queue + unset "functions[-z4h-install-many-parallel]" + [[ -z $success ]] || zf_rm -f -- $success +} diff --git a/fn/-z4h-install-one b/fn/-z4h-install-one new file mode 100644 index 0000000..72ae1bc --- /dev/null +++ b/fn/-z4h-install-one @@ -0,0 +1,150 @@ +#!/usr/bin/env zsh + +local url postinstall command + +if [[ $1 == */* ]]; then + if [[ $1 == *@* ]]; then + url=https://github.com/${1%%@*}/archive/${1#*@}.tar.gz + 1=${1%%@*} + else + url=https://github.com/$1/archive/master.tar.gz + fi + if ! zstyle -s :z4h:$1 postinstall postinstall; then + case $1 in + ohmyzsh/ohmyzsh) + postinstall=-z4h-postinstall-ohmyzsh + ;; + MichaelAquilina/zsh-you-should-use) + postinstall=-z4h-postinstall-zsh-you-should-use + ;; + wfxr/forgit) + postinstall=-z4h-postinstall-forgit + ;; + changyuheng/fz) + postinstall=-z4h-postinstall-fz + ;; + zsh-users/(zsh-syntax-highlighting|zsh-autosuggestions|zsh-history-substring-search)) + postinstall=-z4h-postinstall-zsh-users + ;; + *) + postinstall= + ;; + esac + fi +else + local -a channel + zstyle -a :z4h:$1 channel channel || channel=(stable) + local tmux_cmd='sh -- '${(q)Z4H}/zsh4humans/sc/install-tmux' -d "${Z4H_PACKAGE_DIR}" -q' + tmux_cmd+=' && print -r -- "$EPOCHREALTIME" >"${Z4H_PACKAGE_DIR}"/stamp' + local brew_cmd='command brew tap --quiet homebrew/command-not-found && zf_mkdir -- "${Z4H_PACKAGE_DIR}"' + case $#channel-$channel[1] in + 2-command) command=$channel[2];; + 1-stable|1-testing) + case $1 in + tmux) command=$tmux_cmd;; + homebrew-command-not-found) command=$brew_cmd;; + terminfo) url=https://github.com/romkatv/terminfo/archive/v1.4.0.tar.gz;; + *) url=https://github.com/zsh4humans/$1/archive/z4h-$channel[1].tar.gz;; + esac + ;; + 1-dev) + case $1 in + fzf) url=https://github.com/junegunn/$1/archive/master.tar.gz;; + powerlevel10k) url=https://github.com/romkatv/$1/archive/master.tar.gz;; + systemd) url=https://github.com/systemd/$1/archive/master.tar.gz;; + zsh-completions) url=https://github.com/zsh-users/$1/archive/master.tar.gz;; + zsh-autosuggestions) url=https://github.com/zsh-users/$1/archive/master.tar.gz;; + zsh-syntax-highlighting) url=https://github.com/zsh-users/$1/archive/master.tar.gz;; + zsh-history-substring-search) url=https://github.com/zsh-users/$1/archive/master.tar.gz;; + terminfo) url=https://github.com/romkatv/terminfo/archive/v1.3.0.tar.gz;; + tmux) command=$tmux_cmd;; + homebrew-command-not-found) command=$brew_cmd;; + *) + print -Pru2 -- "%F{3}z4h%f: %F{1}internal error%f: unknown package: ${1//\%/%%}" + return 1 + ;; + esac + ;; + 1-none) + zf_mkdir -p -- $Z4H/$1 || return + return + ;; + *) + print -Pru2 -- '%F{3}z4h%f: invalid zstyle' + print -Pru2 -- '' + print -Pru2 -- " %F{2}zstyle%f :z4h:${(q)1//\%/%%} channel %F{1}${(j: :)${(@q)channel//\%/%%}}%f" + print -Pru2 -- '' + print -Pru2 -- 'Supported values: %Bstable%b, %Btesting%b and %Bdev%b.' + return 1 + ;; + esac + (( $+functions[-z4h-postinstall-$1] )) && postinstall=-z4h-postinstall-$1 +fi + +local suf +[[ $1 == systemd ]] && suf=' completions' + +if [[ -e $Z4H/.updating ]]; then + print -Pru2 -- "%F{3}z4h%f: updating %B${1//\%/%%}%b$suf" +else + print -Pru2 -- "%F{3}z4h%f: installing %B${1//\%/%%}%b$suf" +fi + +local dst=$Z4H/$1 + +zf_mkdir -p -- ${dst:h} 2>/dev/null || zf_mkdir -p -- ${dst:h} || return + +local old new + +{ + if (( $+commands[mktemp] )); then + old="$(command mktemp -d -- $dst.old.XXXXXXXXXX)" || return + new="$(command mktemp -d -- $dst.new.XXXXXXXXXX)" || return + else + old=$dst.old.$$ + new=$dst.new.$$ + zf_rm -rf -- $old $new || return + zf_mkdir -p -- $old $new || return + fi + + local Z4H_PACKAGE_NAME=$1 Z4H_PACKAGE_DIR=$new/${1:t} + + if [[ -n $command ]]; then + () { eval $command } || return 1 + if [[ ! -d $Z4H_PACKAGE_DIR ]]; then + print -Pru2 -- "%F{3}z4h%f: custom command failed to install: %F{1}${Z4H_PACKAGE_DIR//\%/%%}%f" + return 1 + fi + else + local err + if (( $+commands[curl] )); then + err="$(command curl -fsSL -- $url 2>&1 >$new/snapshot.tar.gz)" + elif (( $+commands[wget] )); then + err="$(command wget -O- -- $url 2>&1 >$new/snapshot.tar.gz)" + else + print -Pru2 -- "%F{3}z4h%f: please install %F{1}curl%f or %F{1}wget%f" + return 1 + fi + if (( $? )); then + print -ru2 -- $err + print -Pru2 -- "%F{3}z4h%f: failed to download %F{1}${url//\%/%%}%f" + return 1 + fi + command tar -C $new -xzf $new/snapshot.tar.gz || return + local dirs=($new/*-*(N/)) + if (( $#dirs != 1 )); then + print -Pru2 -- "%F{3}z4h%f: invalid content: %F{1}${url//\%/%%}%f" + return 1 + fi + if [[ $dirs[1] != $new/${1:t} ]]; then + -z4h-mv $dirs[1] $new/${1:t} || return + fi + fi + + eval $postinstall || return + + [[ ! -e $dst ]] || -z4h-mv $dst $old/${1:t} 2>/dev/null || zf_rm -rf -- $dst || return + -z4h-mv $new/${1:t} $dst || return +} always { + [[ -z $old && -z $new ]] || zf_rm -rf -- $old $new +} diff --git a/fn/-z4h-is-valid-list b/fn/-z4h-is-valid-list new file mode 100644 index 0000000..4c3ab77 --- /dev/null +++ b/fn/-z4h-is-valid-list @@ -0,0 +1,23 @@ +#!/usr/bin/env zsh +# +# Succeeds if $1 is a well-formed command. + +# If it ends with an odd number of backslashes, it's malformed. +() { + builtin emulate -L zsh -o extended_glob + [[ $1 == (|*[^\\])(\\\\)#\\ ]] +} "$1" && builtin return 1 + +if [[ -v functions[-z4h-test-func] ]]; then + builtin unfunction -- -z4h-test-func +fi +functions[-z4h-test-func]="$1" 2>/dev/null || builtin return 1 +[[ -v functions[-z4h-test-func] ]] || builtin return 1 +builtin unfunction -- -z4h-test-func +# This suffix allows us to detect two tricky cases: "for x" and "</dev/null || builtin return 0 +[[ -v functions[-z4h-test-func] ]] || builtin return 0 +builtin unfunction -- -z4h-test-func +builtin return 1 diff --git a/fn/-z4h-main-complete b/fn/-z4h-main-complete new file mode 100644 index 0000000..3af60db --- /dev/null +++ b/fn/-z4h-main-complete @@ -0,0 +1,120 @@ +#!/usr/bin/env zsh + +# Based on _main_complete from Zsh 5.8. + +local IFS=$' \t\n\0' +eval "$_comp_setup" + +local func funcs ret=1 tmp _compskip format nm call match min max i num \ + _completers _completer _completer_num curtag _comp_force_list \ + _matchers _matcher _c_matcher _matcher_num _comp_tags _comp_mesg \ + mesg str context state state_descr line opt_args val_args \ + curcontext="$curcontext" \ + _last_nmatches=-1 _last_menu_style _def_menu_style _menu_style sel \ + _tags_level=0 \ + _saved_exact="${compstate[exact]}" \ + _saved_lastprompt="${compstate[last_prompt]}" \ + _saved_list="${compstate[list]}" \ + _saved_insert="${compstate[insert]}" \ + _saved_colors="$ZLS_COLORS" \ + _saved_colors_set=${+ZLS_COLORS} \ + _ambiguous_color= + +local _comp_priv_prefix ZLS_COLORS ZLS_COLOURS +unset _comp_priv_prefix ZLS_COLORS ZLS_COLOURS + +local -a precommands +local -ar builtin_precommands=(- builtin eval exec nocorrect noglob time) +typeset -U _lastdescr _comp_ignore _comp_colors + +[[ -z "$curcontext" ]] && curcontext=::: + +if [[ -z "$compstate[quote]" ]]; then + if [[ -o equals ]] && compset -P 1 '='; then + compstate[context]=equal + elif [[ "$PREFIX" != */* && "$PREFIX[1]" = '~' ]]; then + compset -p 1 + compstate[context]=tilde + fi +fi + +compstate[list]= +compstate[exact]= +compstate[insert]=unambiguous + +_completers=(_complete) + +_completer_num=1 + +integer SECONDS=0 +TRAPINT () { + zle -M "Killed by signal in ${funcstack[2]} after ${SECONDS}s" + zle -R + return 130 +} +TRAPQUIT () { + zle -M "Killed by signal in ${funcstack[2]} after ${SECONDS}s" + zle -R + return 131 +} + +funcs=("$compprefuncs[@]") +compprefuncs=() +for func in "$funcs[@]"; do + "$func" +done + +for tmp in "$_completers[@]"; do + if [[ -n "$call" ]]; then + _completer="${tmp}" + elif [[ "$tmp" = *:-* ]]; then + _completer="${${tmp%:*}[2,-1]//_/-}${tmp#*:}" + tmp="${tmp%:*}" + elif [[ $tmp = *:* ]]; then + _completer="${tmp#*:}" + tmp="${tmp%:*}" + else + _completer="${tmp[2,-1]//_/-}" + fi + curcontext="${curcontext/:[^:]#:/:${_completer}:}" + zstyle -a ":completion:${curcontext}:" matcher-list _matchers || _matchers=('') + _matcher_num=1 + _matcher= + for _c_matcher in "$_matchers[@]"; do + if [[ "$_c_matcher" == +* ]]; then + _matcher="$_matcher $_c_matcher[2,-1]" + else + _matcher="$_c_matcher" + fi + _comp_mesg= + if [[ -n "$call" ]]; then + if "${(@)argv[3,-1]}"; then + ret=0 + break 2 + fi + elif "$tmp"; then + ret=0 + break 2 + fi + (( _matcher_num++ )) + done + [[ -n "$_comp_mesg" ]] && break + (( _completer_num++ )) +done + +curcontext="${curcontext/:[^:]#:/::}" +nm=$compstate[nmatches] + +if [[ -n $compstate[quote] ]]; then + typeset -g _z4h_in_quotes=1 +fi + +# typeset -pm '_z4h_words|_z4h_descrs|_z4h_scaffolds|compstate' >&2 + +funcs=("$comppostfuncs[@]") +comppostfuncs=() +for func in "$funcs[@]"; do + "$func" +done + +return ret diff --git a/fn/-z4h-move-and-kill b/fn/-z4h-move-and-kill new file mode 100644 index 0000000..901d901 --- /dev/null +++ b/fn/-z4h-move-and-kill @@ -0,0 +1,26 @@ +#!/usr/bin/env zsh + +local -i cursor=CURSOR +"$@" +emulate -L zsh + +local -i from to +if (( CURSOR < cursor )); then + from=CURSOR+1 + to=cursor +else + from=cursor+1 + to=CURSOR + CURSOR=$cursor +fi + +if [[ $LASTWIDGET != ((z4h-|.|)(backward-|)kill-*|(.|)copy-region-as-kill) ]]; then + zle .copy-region-as-kill -- $BUFFER[from,to] +elif (( CURSOR < cursor )); then + CUTBUFFER=$BUFFER[from,to]$CUTBUFFER +else + CUTBUFFER+=$BUFFER[from,to] +fi + +BUFFER[from,to]= +zle -f kill diff --git a/fn/-z4h-mv b/fn/-z4h-mv new file mode 100644 index 0000000..15584fc --- /dev/null +++ b/fn/-z4h-mv @@ -0,0 +1,21 @@ +#!/usr/bin/env zsh + +(( ARGC == 2 )) || return '_z4h_err()' +[[ $2 != */ ]] || return '_z4h_err()' + +zf_mv -f -- "$1" "$2" 2>/dev/null && return +command mv -f -- "$1" "$2" 2>/dev/null && return + +[[ -e $1 ]] || return '_z4h_err()' +[[ -d ${2:h} ]] || return '_z4h_err()' + +if [[ -e $2 ]]; then + zf_rm -rf -- "$2" || return +fi + +if ! command cp -r -- "$1" "$2"; then + [[ ! -e $2 ]] || zf_rm -rf -- "$2" || return + return 1 +fi + +zf_rm -rf -- "$1" diff --git a/fn/-z4h-osc9 b/fn/-z4h-osc9 new file mode 100644 index 0000000..7d11186 --- /dev/null +++ b/fn/-z4h-osc9 @@ -0,0 +1,35 @@ +#!/usr/bin/env zsh +# +# Docs: +# +# https://learn.microsoft.com/en-us/windows/terminal/tutorials/new-tab-same-directory#zsh +# +# The recommended code looks like this: +# +# keep_current_path() { +# printf "\e]9;9;%s\e\\" "$(wslpath -w "$PWD")" +# } +# precmd_functions+=(keep_current_path) + +emulate -L zsh -o no_multi_byte + +[[ -v commands[wslpath] ]] || return + +if [[ $PWD == /* && $PWD -ef . ]]; then + local cwd=$PWD +else + local cwd=${${:-.}:a} +fi +[[ -n $cwd ]] || return + +typeset -gA _z4h_lin2win_cwd +local win_cwd=${_z4h_lin2win_cwd[$cwd]-} + +if [[ -z $win_cwd ]]; then + win_cwd="$(command wslpath -am . 2>/dev/null)x" || return + win_cwd=${win_cwd[1,-2]//$'\e'/$'\e\e'} + [[ -n $win_cwd ]] || return + _z4h_lin2win_cwd[$cwd]=$win_cwd +fi + +-z4h-tmux-bypass '\e]9;9;%s\e\\' "$win_cwd" diff --git a/fn/-z4h-postinstall-forgit b/fn/-z4h-postinstall-forgit new file mode 100644 index 0000000..85f2619 --- /dev/null +++ b/fn/-z4h-postinstall-forgit @@ -0,0 +1,19 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +local file=$Z4H_PACKAGE_DIR/forgit.plugin.zsh +[[ -e $file ]] || return 0 + +local code +code=$(<$file) || return + +local orig=" +set | awk -F '=' '{ print \$1 }' | grep FORGIT_ | while read -r var; do + if ! export | grep -q \"\\(^\$var=\\|^export \$var=\\)\"; then +" +local sub=' +for var in "${(@)parameters[(I)FORGIT_*]}"; do + if [[ ${parameters[$var]} != *export* ]]; then +' +print -r -- ${code/$orig/$sub} >$file diff --git a/fn/-z4h-postinstall-fz b/fn/-z4h-postinstall-fz new file mode 100644 index 0000000..6d536ef --- /dev/null +++ b/fn/-z4h-postinstall-fz @@ -0,0 +1,18 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +local file=$Z4H_PACKAGE_DIR/fz.plugin.zsh +[[ -e $file ]] || return 0 + +local code +code=$(<$file) || return + +local orig=' +__fz_init_zsh_completion() { +' +local sub=' +__fz_init_zsh_completion() { + [[ -n ${ZSH_SCRIPT+X}${ZSH_EXECUTION_STRING+X} ]] && return +' +print -r -- ${code/$orig/$sub} >$file diff --git a/fn/-z4h-postinstall-fzf b/fn/-z4h-postinstall-fzf new file mode 100644 index 0000000..82d8979 --- /dev/null +++ b/fn/-z4h-postinstall-fzf @@ -0,0 +1,59 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +if [[ -e $Z4H/.updating ]]; then + print -ru2 -- ${(%):-"%F{3}z4h%f: updating %Bfzf%b binary"} +else + print -ru2 -- ${(%):-"%F{3}z4h%f: fetching %Bfzf%b binary"} +fi + +local uname_sm +[[ ! -v commands[uname] ]] || uname_sm=$(uname -sm) || return + +local -i installed + +case $uname_sm in + 'Darwin arm64') + local v=0.25.1 + local url=https://github.com/zsh4humans/fzf/releases/download/$v/fzf-$v-darwin_arm8.tar.gz + ;; + 'Linux i686') + local v=0.22.0 + local url=https://github.com/junegunn/fzf-bin/releases/download/$v/fzf-$v-linux_386.tgz + ;; + *) + local BASH_SOURCE=($Z4H_PACKAGE_DIR/install) err + if ! err=$(local opt && emulate sh && set -- --bin && + builtin source "${BASH_SOURCE[0]}" 2>&1); then + print -ru2 -- $err + return 1 + fi + installed=1 + ;; +esac + +if (( ! installed )); then + local err archive=$Z4H_PACKAGE_DIR/fzf.tar.gz + if (( $+commands[curl] )); then + err="$(command curl -fsSL -- $url 2>&1 >$archive)" + elif (( $+commands[wget] )); then + err="$(command wget -O- -- $url 2>&1 >$archive)" + else + print -Pru2 -- "%F{3}z4h%f: please install %F{1}curl%f or %F{1}wget%f" + return 1 + fi + if (( $? )); then + print -ru2 -- $err + print -Pru2 -- "%F{3}z4h%f: failed to download %F{1}${url//\%/%%}%f" + return 1 + fi + [[ -e $Z4H_PACKAGE_DIR/bin ]] || zf_mkdir -- $Z4H_PACKAGE_DIR/bin || return + command tar -C $Z4H_PACKAGE_DIR/bin -xzf $archive || return + zf_rm -- $archive || return +fi + +if [[ -h $Z4H_PACKAGE_DIR/bin/fzf ]]; then + command cp -- $Z4H_PACKAGE_DIR/bin/fzf $Z4H_PACKAGE_DIR/bin/fzf.tmp || return + zf_mv -f -- $Z4H_PACKAGE_DIR/bin/fzf.tmp $Z4H_PACKAGE_DIR/bin/fzf || return +fi diff --git a/fn/-z4h-postinstall-ohmyzsh b/fn/-z4h-postinstall-ohmyzsh new file mode 100644 index 0000000..d88315b --- /dev/null +++ b/fn/-z4h-postinstall-ohmyzsh @@ -0,0 +1,5 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +z4h compile -- $Z4H_PACKAGE_DIR/{oh-my-zsh.sh,**/*.zsh}(.N) diff --git a/fn/-z4h-postinstall-powerlevel10k b/fn/-z4h-postinstall-powerlevel10k new file mode 100644 index 0000000..b9502fe --- /dev/null +++ b/fn/-z4h-postinstall-powerlevel10k @@ -0,0 +1,21 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +zf_mkdir -p -- $Z4H/cache/powerlevel10k/p10k-root || return + +if [[ $GITSTATUS_AUTO_INSTALL != 0 ]]; then + if [[ -e $Z4H/.updating ]]; then + print -ru2 -- ${(%):-"%F{3}z4h%f: updating %Bgitstatus%b binary"} + else + print -ru2 -- ${(%):-"%F{3}z4h%f: fetching %Bgitstatus%b binary"} + fi + + ( + unset -m 'GITSTATUS_*~GITSTATUS_CACHE_DIR' + export GITSTATUS_CACHE_DIR=$GITSTATUS_CACHE_DIR + $Z4H_PACKAGE_DIR/gitstatus/install -f + ) || return +fi + +z4h compile -- $Z4H_PACKAGE_DIR/{*.zsh-theme,internal/*.zsh,gitstatus/*.zsh,gitstatus/install}(N) diff --git a/fn/-z4h-postinstall-self b/fn/-z4h-postinstall-self new file mode 100644 index 0000000..ac295a8 --- /dev/null +++ b/fn/-z4h-postinstall-self @@ -0,0 +1,14 @@ +#!/usr/bin/env zsh + +z4h compile -- $Z4H/zsh4humans/{main.zsh,sc/exec-zsh-i,fn/(|-|_)z4h[^.]#~*.zwc} $Z4H/z4h.zsh || return + +[[ -v functions[compinit] ]] && unfunction compinit +autoload -Uz +X compinit || return +local orig="bindkey '^i' | IFS=\$' \t' read -A _i_line" +local replacement="_i_line=( '\"^I\"' z4h-fzf-complete )" +print -r -- ${functions[compinit]/$orig/$replacement} >$Z4H/zsh4humans/fn/tmp.$$.compinit || return +zf_mv -- $Z4H/zsh4humans/fn/tmp.$$.compinit $Z4H/zsh4humans/fn/-z4h-compinit-impl || return +z4h compile -- $Z4H/zsh4humans/fn/-z4h-compinit-impl || return +autoload -Uz -- -z4h-compinit-impl + +function compinit() {} diff --git a/fn/-z4h-postinstall-systemd b/fn/-z4h-postinstall-systemd new file mode 100644 index 0000000..0b5c276 --- /dev/null +++ b/fn/-z4h-postinstall-systemd @@ -0,0 +1,30 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +local comp=$Z4H_PACKAGE_DIR/shell-completion/zsh/_systemctl +[[ -e $comp.in ]] || return 0 +[[ -v commands[systemd-path] ]] || return 0 + +local lib +lib=$(systemd-path system-library-private) || return + +if [[ $lib != /* ]]; then + print -Pru2 -- '%F{3}z4h%f: unexpected command output: %F{1}systemd-path system-library-private%f' + return 1 +fi + +if [[ -x /usr/lib/systemd/systemd ]]; then + local systemd=/usr/lib/systemd +elif [[ -x /lib/systemd/systemd ]]; then + local systemd=/lib/systemd +else + print -Pru2 -- '%F{3}z4h%f: command not found: %F{1}systemd%f' + return 1 +fi + +local content +content=$(<$comp.in) + +print -r -- ${${content//@rootlibexecdir@/$systemd}//\\@/@} >$comp +zf_rm -- $comp.in diff --git a/fn/-z4h-postinstall-terminfo b/fn/-z4h-postinstall-terminfo new file mode 100644 index 0000000..86a30ab --- /dev/null +++ b/fn/-z4h-postinstall-terminfo @@ -0,0 +1,27 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +# Fork to avoid triggering https://www.zsh.org/mla/workers/2020/msg00588.html +( + # ${(L):-I} is ı in Turkish locale. + LC_ALL=C + + local pat hex char + for pat in "[^A-Z]" "[A-Z]"; do + for hex in $Z4H_PACKAGE_DIR/[[:xdigit:]][[:xdigit:]](:t); do + printf -v char "\\x$hex" + [[ $char == $~pat ]] || continue + [[ -e ~/.terminfo/$hex ]] || zf_mkdir -p -- ~/.terminfo/$hex || return + cp -- $Z4H_PACKAGE_DIR/$hex/* ~/.terminfo/$hex/ || return + if [[ $char == [a-z] || ! ~/.terminfo/$char -ef ~/.terminfo/${(L)char}] ]]; then + [[ -e ~/.terminfo/$char ]] || + zf_ln -s -- $hex ~/.terminfo/$char 2>/dev/null || + zf_mkdir -p -- ~/.terminfo/$char || + return + cp -- $Z4H_PACKAGE_DIR/$hex/* ~/.terminfo/$char/ || return + fi + zf_rm -rf -- $Z4H_PACKAGE_DIR/$hex || return + done + done +) diff --git a/fn/-z4h-postinstall-zsh-autosuggestions b/fn/-z4h-postinstall-zsh-autosuggestions new file mode 100644 index 0000000..cd6d635 --- /dev/null +++ b/fn/-z4h-postinstall-zsh-autosuggestions @@ -0,0 +1,5 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +z4h compile -- $Z4H_PACKAGE_DIR/{zsh-autosuggestions.zsh,src/**/*.zsh}(N) diff --git a/fn/-z4h-postinstall-zsh-history-substring-search b/fn/-z4h-postinstall-zsh-history-substring-search new file mode 100644 index 0000000..60b610c --- /dev/null +++ b/fn/-z4h-postinstall-zsh-history-substring-search @@ -0,0 +1,5 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +z4h compile -- $Z4H_PACKAGE_DIR/zsh-history-substring-search.zsh diff --git a/fn/-z4h-postinstall-zsh-syntax-highlighting b/fn/-z4h-postinstall-zsh-syntax-highlighting new file mode 100644 index 0000000..ffef9d1 --- /dev/null +++ b/fn/-z4h-postinstall-zsh-syntax-highlighting @@ -0,0 +1,5 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +z4h compile -- $Z4H_PACKAGE_DIR/{zsh-syntax-highlighting.zsh,highlighters/*/*.zsh}(N) diff --git a/fn/-z4h-postinstall-zsh-users b/fn/-z4h-postinstall-zsh-users new file mode 100644 index 0000000..e992aa2 --- /dev/null +++ b/fn/-z4h-postinstall-zsh-users @@ -0,0 +1,19 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +local file=$Z4H_PACKAGE_DIR/${Z4H_PACKAGE_NAME:t}.zsh +[[ -e $file ]] || return 0 + +local code +code=$(<$file) || return + +local orig=' + +' +local sub=' + +[[ -n ${ZSH_SCRIPT+X}${ZSH_EXECUTION_STRING+X} ]] && return + +' +print -r -- ${code/$orig/$sub} >$file diff --git a/fn/-z4h-postinstall-zsh-you-should-use b/fn/-z4h-postinstall-zsh-you-should-use new file mode 100644 index 0000000..8b409f6 --- /dev/null +++ b/fn/-z4h-postinstall-zsh-you-should-use @@ -0,0 +1,25 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +local file=$Z4H_PACKAGE_DIR/zsh-you-should-use.plugin.zsh +[[ -e $file ]] || return 0 + +local code +code=$(<$file) || return + +local orig=' + NONE="$(tput sgr0)" + BOLD="$(tput bold)" + RED="$(tput setaf 1)" + YELLOW="$(tput setaf 3)" + PURPLE="$(tput setaf 5)" +' +local sub=" + NONE=\$'\\e[0m' + BOLD=\$'\\e[1m' + RED=\$'\\e[31m' + YELLOW=\$'\\e[33m' + PURPLE=\$'\\e[35m' +" +print -r -- ${code/$orig/$sub} >$file diff --git a/fn/-z4h-present-files b/fn/-z4h-present-files new file mode 100644 index 0000000..c5dfdf6 --- /dev/null +++ b/fn/-z4h-present-files @@ -0,0 +1,148 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" +setopt octalzeroes + +local -i list_colors=$1 +local -i list_types=$2 +local -i tilde_unexpand=$3 + +[[ $_z4h_mode_colors[ln] != target ]] +local -i ln_target=$? + +local -r nil=$'\0' +local -r or=$_z4h_mode_colors[or] +local -r ln=$_z4h_mode_colors[ln] +local -r suf=('|' '|' '' '' '' '' '/' '/' '' '' '' '' '' '' '' '*' '' '' '@' '@' '' '' '=' '=') + +local -i10 m i +local buf file style +local -a files modes link link_mode +local -i MATCH +local MBEGIN MEND + +while true; do + while [[ $buf != *$'\n'* ]]; do + sysread -s 4096 'buf[$#buf+1]' && continue + (( $? == 5 )) || return + return + done + + files=("${(@f)buf}") + buf=$files[-1] + files[-1]=() + + if (( ! list_colors && ! list_types && ! tilde_unexpand )) || ! zstat -L -A modes +mode -- $files; then + printf '%1$s\0%1$q\n' $files + continue + fi + + if (( ! list_colors && ! tilde_unexpand )); then + printf '%2$s\0%2$q%1$s\n' \ + ${"${(@)modes/(#m)*/$suf[$((((MATCH & 61440) >> 11) | !(MATCH & 73)))]}":^files} + continue + fi + + # This doesn't work the same way as GNU ls. See get_color_indicator in + # https://git.savannah.gnu.org/cgit/coreutils.git/tree/src/ls.c. + # + # TODO: fix it. + # TODO: see if this can be made faster by invoking `find` with `-printf '%Y\0%m\0%P\n'`, + # invoking `zstat -A links +link -- ${files*$'\0'*$'\0'}` and then applying some kind of + # crazy expansion. + # + # Types: + # + # ? unknown + # p fifo + # c chardev + # d directory + # b blockdev + # - normal + # s socket + # w whiteout + # l symlink (good) + # L symlink (loop) + # N symlink (nonexistent) + + if (( tilde_unexpand )); then + # This implementation is the same as below except that $file in the second output field + # is replaced with ${(D)file}. + for file m in ${files:^modes}; do + if [[ ${style::=${${_z4h_mode_codes[$((m & 64075))]:-$'\0'}/%$'\0'/$'\0'$_z4h_name_colors[(ke)$file:t]}} \ + == $'@\0.' ]]; then + if ! zstat -L -A link +link -- $file; then + printf '%1$s\0\e[%2$sm%3$s\e[0m\n' $file "${or:-$_z4h_name_colors[$file:t]}" ${(D)file} + elif ! zstat -A link_mode +mode -- $file; then + printf '%1$s\0\e[%2$sm%5$s\e[0m -> \e[%4$sm%3$q\e[0m\n' \ + $file "${or:-$_z4h_name_colors[$file:t]}" \ + $link "${or:-$_z4h_name_colors[$link:t]}" \ + ${(D)file} + elif (( ln_target )); then + printf '%1$s\0\e[%4$sm%5$s\e[0m -> \e[%4$sm%2$q\e[0m%3$s\n' \ + $file $link \ + "${(@0)${${_z4h_mode_codes[$((link_mode & 64075))]:-$nil}/%$nil/$nil$_z4h_name_colors[(ke)$link:t]}}" \ + ${(D)file} + else + printf '%1$s\0\e[%3$sm%6$s\e[0m -> \e[%5$sm%2$q\e[0m%4$s\n' \ + $file $link "${ln:-$_z4h_name_colors[$link:t]}" \ + "${(@0)${${_z4h_mode_codes[$((link_mode & 64075))]:-$nil}/%$nil/$nil$_z4h_name_colors[(ke)$link:t]}}" \ + ${(D)file} + fi + else + printf '%1$s\0\e[%3$sm%4$s\e[0m%2$s\n' $file "${(@0)style}" ${(D)file} + fi + done + elif (( $#_z4h_name_colors )); then + # This implementation works whether _z4h_name_colors is empty or not. It's about 30% + # slower than the alternative implementation below, which only works if _z4h_name_colors + # is empty. + for file m in ${files:^modes}; do + if [[ ${style::=${${_z4h_mode_codes[$((m & 64075))]:-$'\0'}/%$'\0'/$'\0'$_z4h_name_colors[(ke)$file:t]}} \ + == $'@\0.' ]]; then + if ! zstat -L -A link +link -- $file; then + printf '%1$s\0\e[%2$sm%1$q\e[0m\n' $file "${or:-$_z4h_name_colors[$file:t]}" + elif ! zstat -A link_mode +mode -- $file; then + printf '%1$s\0\e[%2$sm%1$q\e[0m -> \e[%4$sm%3$q\e[0m\n' \ + $file "${or:-$_z4h_name_colors[$file:t]}" \ + $link "${or:-$_z4h_name_colors[$link:t]}" + elif (( ln_target )); then + printf '%1$s\0\e[%4$sm%1$q\e[0m -> \e[%4$sm%2$q\e[0m%3$s\n' \ + $file $link \ + "${(@0)${${_z4h_mode_codes[$((link_mode & 64075))]:-$nil}/%$nil/$nil$_z4h_name_colors[(ke)$link:t]}}" + else + printf '%1$s\0\e[%3$sm%1$q\e[0m -> \e[%5$sm%2$q\e[0m%4$s\n' \ + $file $link "${ln:-$_z4h_name_colors[$link:t]}" \ + "${(@0)${${_z4h_mode_codes[$((link_mode & 64075))]:-$nil}/%$nil/$nil$_z4h_name_colors[(ke)$link:t]}}" + fi + else + printf '%1$s\0\e[%3$sm%1$q\e[0m%2$s\n' $file "${(@0)style}" + fi + done + else + modes=("${(@)modes:/(#m)*/${_z4h_mode_codes[$((MATCH & 64075))]:-$nil}}") + while true; do + i=${modes[(i)@*]} + if (( i == $#modes + 1 )); then + printf '%3$s\0\e[%2$sm%3$q\e[0m%1$s\n' "${(@0)${(@)modes:^files}}" + break + elif (( i != 1 )); then + printf '%3$s\0\e[%2$sm%3$q\e[0m%1$s\n' "${(@0)${(@)modes[1,i-1]:^files}}" + shift $((i-1)) modes files + fi + file=$files[1] + shift modes files + if ! zstat -L -A link +link -- $file; then + printf '%1$s\0\e['$or'm%1$q\e[0m\n' $file + elif ! zstat -A link_mode +mode -- $file; then + printf '%1$s\0\e['$or'm%1$q\e[0m -> \e['$or'm%2$q\e[0m\n' $file $link + elif (( ln_target )); then + printf '%1$s\0\e[%4$sm%1$q\e[0m -> \e[%4$sm%2$q\e[0m%3$s\n' \ + $file $link "${(@0)${_z4h_mode_codes[$((link_mode & 64075))]:-$nil}}" + else + printf '%1$s\0\e['$ln'm%1$q\e[0m -> \e[%4$sm%2$q\e[0m%3$s\n' \ + $file $link "${(@0)${_z4h_mode_codes[$((link_mode & 64075))]:-$nil}}" + fi + done + fi +done diff --git a/fn/-z4h-prompt-length b/fn/-z4h-prompt-length new file mode 100644 index 0000000..55cd9f6 --- /dev/null +++ b/fn/-z4h-prompt-length @@ -0,0 +1,15 @@ +#!/usr/bin/env zsh + +local -i COLUMNS=1024 +local -i x y=${#1} m +if (( y )); then + while (( ${${(%):-$1%$y(l.1.0)}[-1]} )); do + x=y + (( y *= 2 )) + done + while (( y > x + 1 )); do + (( m = x + (y - x) / 2 )) + (( ${${(%):-$1%$m(l.x.y)}[-1]} = m )) + done +fi +typeset -g REPLY=$x diff --git a/fn/-z4h-read-dir-history b/fn/-z4h-read-dir-history new file mode 100644 index 0000000..26d0428 --- /dev/null +++ b/fn/-z4h-read-dir-history @@ -0,0 +1,9 @@ +#!/usr/bin/env zsh + +local content +local file=$Z4H/stickycache/dir-history-$EUID +if [[ -r $file ]] && content=$(<$file); then + typeset -ga _z4h_dir_history=(${(f)content}) +else + typeset -ga _z4h_dir_history=() +fi diff --git a/fn/-z4h-redraw-buffer b/fn/-z4h-redraw-buffer new file mode 100644 index 0000000..7109a0e --- /dev/null +++ b/fn/-z4h-redraw-buffer @@ -0,0 +1,13 @@ +#!/usr/bin/env zsh + +if (( ${+_z4h_redraw_fd} )); then + zle -F "$_z4h_redraw_fd" + exec {_z4h_redraw_fd}>&- + unset _z4h_redraw_fd +fi + +if [[ -v region_highlight && + ( ${LASTWIDGET-} != accept-line || ${WIDGET-} != zle-line-pre-redraw ) ]]; then + _zsh_highlight + -z4h-autosuggest-fetch +fi diff --git a/fn/-z4h-redraw-prompt b/fn/-z4h-redraw-prompt new file mode 100644 index 0000000..6376fc2 --- /dev/null +++ b/fn/-z4h-redraw-prompt @@ -0,0 +1,18 @@ +#!/usr/bin/env zsh + +{ + -z4h-cursor-hide + local f + for f in chpwd "${chpwd_functions[@]}" precmd "${(@)precmd_functions:#-z4h-direnv-hook}"; do + [[ "${+functions[$f]}" == 0 ]] || "$f" &>/dev/null || true + done + if (( _z4h_use[zsh-syntax-highlighting] )); then + typeset -g _ZSH_HIGHLIGHT_PRIOR_BUFFER= + typeset -gi _ZSH_HIGHLIGHT_PRIOR_CURSOR=0 + fi + zle .reset-prompt + (( ${1:-0} )) || zle -R +} always { + (( ${1:-0} )) || -z4h-cursor-show + -z4h-update-dir-history +} diff --git a/fn/-z4h-replace-buf b/fn/-z4h-replace-buf new file mode 100644 index 0000000..7e4ffd6 --- /dev/null +++ b/fn/-z4h-replace-buf @@ -0,0 +1,40 @@ +#!/usr/bin/env zsh +# +# Usage: -z4h-replace-buf START END REPLACEMENT +# +# Replaces BUFFER[START,END] with REPLACEMENT while keeping the cursor +# pointing to the same content as before. + +emulate -L zsh + +local -i start=$1 end=$2 +local orig_word=$BUFFER[start,end] new_word=$3 + +local reply +if (( $#orig_word * $#new_word <= 10000 )); then + -z4h-string-diff "$orig_word" "$new_word" +else + reply=("$orig_word" "$new_word") +fi + +local -i orig_cur='CURSOR - start + 1' cur new_cur +local src dst +for src dst in "${reply[@]}"; do + (( cur += $#src )) + if (( cur < orig_cur )); then + (( new_cur += $#dst - $#src )) + elif (( cur == orig_cur )); then + (( cur == $#orig_word )) && break + (( new_cur += $#dst - $#src )) + else + if (( orig_cur - (cur - $#src) > $#dst )); then + (( new_cur -= orig_cur - (cur - $#src) - $#dst )) + fi + break + fi +done + +(( new_cur += CURSOR )) +BUFFER[start,end]=$new_word +(( CURSOR = new_cur )) +return 0 diff --git a/fn/-z4h-restore-screen b/fn/-z4h-restore-screen new file mode 100644 index 0000000..807b6fa --- /dev/null +++ b/fn/-z4h-restore-screen @@ -0,0 +1,14 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +[[ -v _z4h_tty_fd ]] || return + +if [[ -n $_Z4H_TMUX ]]; then + (( ${+_z4h_saved_screen} )) || return + print -rnu $_z4h_tty_fd -- "$_z4h_saved_screen" +elif [[ -n $Z4H_SSH && -n $_Z4H_SSH_MARKER ]]; then + printf '\001z4h.%s%s' "$_Z4H_SSH_MARKER" 'restore-screen ' >&$_z4h_tty_fd +else + return 1 +fi diff --git a/fn/-z4h-run-process-tree b/fn/-z4h-run-process-tree new file mode 100644 index 0000000..27996ed --- /dev/null +++ b/fn/-z4h-run-process-tree @@ -0,0 +1,42 @@ +#!/usr/bin/env zsh +# +# Based on https://github.com/romkatv/run-process-tree but without +# the preservation of options, patterns, etc. + +emulate -L zsh || return +setopt monitor traps_async pipe_fail no_unset no_bg_nice || return +zmodload zsh/system || return + +local stdout REPLY +exec {stdout}>&1 || return +{ + { + local -i pipe + local sig=(EXIT HUP ILL INT PIPE QUIT TERM ZERR) + local trap=(trap "trap - $sig; kill -- -$sysparams[pid]" $sig) + + exec {pipe}>&1 1>&$stdout || return + $trap + + { + $trap + while command sleep 1 && print -u $pipe .; do; done + } 2>/dev/null & + local -i watchdog=$! + + { + trap - ZERR + exec {pipe}>&- || return + () { "$@" } "$@" + } & + local -i ret + wait $! || ret=$? + + trap "exit $ret" TERM + kill $watchdog || return + wait $watchdog || return + return ret + } | while read; do; done || return +} always { + exec {stdout}>&- +} diff --git a/fn/-z4h-sanitize-word-prefix b/fn/-z4h-sanitize-word-prefix new file mode 100644 index 0000000..b63ac8d --- /dev/null +++ b/fn/-z4h-sanitize-word-prefix @@ -0,0 +1,12 @@ +#!/usr/bin/env zsh + +() { + emulate -L zsh -o extended_glob + [[ $_z4h_word_prefix != *('$'|'\') ]] || return + if [[ $_z4h_word_prefix == *[A-Z]* ]]; then + [[ -z ${(@)*:#$_z4h_word_prefix*} ]] || return + else + [[ -z ${(@)*:#(#i)$_z4h_word_prefix*} ]] || return + fi +} "$@" || _z4h_word_prefix= +_z4h_word_prefix=${_z4h_word_prefix// /\\ } diff --git a/fn/-z4h-save-screen b/fn/-z4h-save-screen new file mode 100644 index 0000000..11e3938 --- /dev/null +++ b/fn/-z4h-save-screen @@ -0,0 +1,13 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +if [[ -n $_Z4H_TMUX ]]; then + local pane + pane="$(TMUX=$_Z4H_TMUX TMUX_PANE=$_Z4H_TMUX_PANE $_Z4H_TMUX_CMD capture-pane -p -e && print -n x)" || return + typeset -g _z4h_saved_screen=$'\e[0m\e[H'${${pane//$'\n'/$'\e[49m\e[K\r\n'}%$'\r\nx'} +elif [[ -n $Z4H_SSH && -n $_Z4H_SSH_MARKER && -v _z4h_tty_fd ]]; then + printf '\001z4h.%s%s' "$_Z4H_SSH_MARKER" 'save-screen ' >&$_z4h_tty_fd +else + return 1 +fi diff --git a/fn/-z4h-set-list-colors b/fn/-z4h-set-list-colors new file mode 100644 index 0000000..68e5023 --- /dev/null +++ b/fn/-z4h-set-list-colors @@ -0,0 +1,57 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" +setopt octalzeroes + +local curcontext=$1 +local list_types=$2 + +local -a list_colors +zstyle -a :completion:$curcontext:default list-colors list_colors || return +(( $#list_colors )) || return + +if [[ ${list_types}${(pj:\0:)list_colors} != $_z4h_last_list_colors ]]; then + # This takes ~20ms. Can be easily optimized. + typeset -g _z4h_last_list_colors=${list_types}${(pj:\0:)list_colors} + typeset -gA _z4h_name_colors=(${(@s:=:)${(@s.:.)list_colors}:#[[:alpha:]][[:alpha:]]=*}) + typeset -gA _z4h_mode_colors=(${(@Ms:=:)${(@s.:.)list_colors}:#[[:alpha:]][[:alpha:]]=*}) + typeset -gA _z4h_mode_codes=() + () { + local -i8 a b c d + local -a codes + local suf + for a in 0140000 0120000 0100000 0060000 0040000 0020000 0010000; do + for b in 0 02000 04000 06000; do + for c in 0 01000 00002 01002; do + for d in 0 0001 0010 0011 0100 0101 0110 0111; do + suf= + codes=() + if (( a == 0120000 )); then + _z4h_mode_codes[$((a|b|c|d))]=$'@\0.' + continue + fi + if (( a == 0100000 )); then + (( d )) && suf='*' + else + if (( a == 0140000 )); then codes+=($_z4h_mode_colors[so]); suf='='; + elif (( a == 0060000 )); then codes+=($_z4h_mode_colors[bd]); suf='' ; + elif (( a == 0040000 )); then codes+=($_z4h_mode_colors[di]); suf='/'; + elif (( a == 0020000 )); then codes+=($_z4h_mode_colors[cd]); suf='' ; + elif (( a == 0010000 )); then codes+=($_z4h_mode_colors[pi]); suf='|'; fi + if (( c == 01000 )); then codes+=($_z4h_mode_colors[st]); + elif (( c == 00002 )); then codes+=($_z4h_mode_colors[ow]); + elif (( c == 01002 )); then codes+=($_z4h_mode_colors[tw]); fi + fi + (( b & 04000 )) && codes+=($modecolors[su]) + (( b & 02000 )) && codes+=($modecolors[sg]) + (( $#codes || a != 0100000 || !d )) || codes+=($_z4h_mode_colors[ex]) + (( list_types )) || suf= + _z4h_mode_codes[$((a|b|c|d))]=$suf$'\0'${(j:;:)codes} + done + done + done + done + } +fi + +return 0 diff --git a/fn/-z4h-set-term-title b/fn/-z4h-set-term-title new file mode 100644 index 0000000..b94a8c1 --- /dev/null +++ b/fn/-z4h-set-term-title @@ -0,0 +1,14 @@ +#!/usr/bin/env zsh + +emulate -L zsh -o no_prompt_bang -o prompt_subst -o prompt_percent + +local title=$1 +shift +print -Prnv title -- $title +printf -v title '\e]0;%s\a' "${(V)title}" + +if [[ -t 1 ]]; then + print -rn -- $title +elif [[ -v _z4h_tty_fd ]]; then + print -rnu $_z4h_tty_fd -- $title +fi diff --git a/fn/-z4h-show-dots b/fn/-z4h-show-dots new file mode 100644 index 0000000..f766487 --- /dev/null +++ b/fn/-z4h-show-dots @@ -0,0 +1,27 @@ +#!/usr/bin/env zsh + +local -i cursor=CURSOR +local postdisplay=$POSTDISPLAY +local buffer=$BUFFER + +BUFFER=$1 +POSTDISPLAY=.. + +if [[ -n $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE ]]; then + local style=$ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE +elif (( terminfo[colors] >= 256 )); then + local style='fg=244' +else + local style='fg=black,bold' +fi + +region_highlight+=("$#BUFFER $(($#BUFFER + $#POSTDISPLAY)) $style") +typeset -gi CURSOR='_z4h_cursor_max()' + +-z4h-cursor-hide +zle -R + +BUFFER=$buffer +CURSOR=cursor +POSTDISPLAY=$postdisplay +region_highlight[-1]=() diff --git a/fn/-z4h-ssh-maybe-update b/fn/-z4h-ssh-maybe-update new file mode 100644 index 0000000..724e5b5 --- /dev/null +++ b/fn/-z4h-ssh-maybe-update @@ -0,0 +1,18 @@ +#!/usr/bin/env zsh + +# Requires: [[ $Z4H_SSH == <1->:* ]] + +eval "$_z4h_opt" + +local min_version=${Z4H_SSH%%:*} + +local got_version +got_version=${$(<$Z4H/zsh4humans/version)%$'\r'} && + [[ $got_version == <-> ]] && + (( got_version >= min_version )) && + return + +print -Pru2 -- "%F{3}z4h%f: %Binitiating update%b (cached version too old)" + +Z4H_SSH=0:${Z4H_SSH#*:} +-z4h-cmd-update diff --git a/fn/-z4h-start-ssh-agent b/fn/-z4h-start-ssh-agent new file mode 100644 index 0000000..d015128 --- /dev/null +++ b/fn/-z4h-start-ssh-agent @@ -0,0 +1,25 @@ +#!/usr/bin/env zsh + +[[ -v commands[ssh-agent] ]] || return 0 + +function -z4h-ssh-agent-running() { + [[ -w $SSH_AUTH_SOCK ]] && builtin kill -0 -- $SSH_AGENT_PID 2>/dev/null +} + +{ + -z4h-ssh-agent-running && return + unset SSH_AGENT_PID SSH_AUTH_SOCK + + local env_file=$Z4H/stickycache/ssh-agent-$EUID + [[ -r $env_file ]] && builtin source $env_file >/dev/null && -z4h-ssh-agent-running && return + + local tmp=$env_file.tmp.$$ + local -a args + zstyle -a :z4h:ssh-agent: extra-args args + command ssh-agent -s $args >$tmp || return + builtin source $tmp >/dev/null || return + -z4h-ssh-agent-running || return + zf_mv -f -- $tmp $env_file || return +} always { + builtin unfunction -- -z4h-ssh-agent-running +} diff --git a/fn/-z4h-string-diff b/fn/-z4h-string-diff new file mode 100644 index 0000000..00b2564 --- /dev/null +++ b/fn/-z4h-string-diff @@ -0,0 +1,84 @@ +#!/usr/bin/env zsh +# +# Finds the difference between two strings using Levenshtein distance. +# Sets array reply to an even number of elements. The concatenation of odd +# elements (one-based) is equal to the first string; even elements -- second +# string. +# +# Example: +# +# % -z4h-string-diff 'sunday!' 'saturday' +# % printf '%-5s => %s\n' ${(qq)reply} +# 's' => 's' +# '' => 'at' +# 'u' => 'u' +# 'n' => 'r' +# 'day' => 'day' +# '!' => '' +# +# Time and memory complexity in a decent language would be O($#1 * $#2). In Zsh +# it's even worse than that. It's a good idea to check the value of $#1 * $#2 +# before invoking this function. It takes about 0.2s if this multiple is 10000. +# +# Note: This is Wagner-Fischer algorithm. + +emulate -L zsh -o no_unset -o ksh_arrays + +local s=$1 t=$2 +local -i m=$#s n=$#t w=$(($#t + 1)) i j p q r x y z e +local -a d=({1..$(((m + 1) * (n + 1)))}) +d=(${d[@]//*/0}) +local -a b=(${d[@]}) + +for ((i = 1; i <= m; ++i)); do + (( d[i*w] = i, b[i*w] = 1 )) +done +for ((j = 1; j <= n; ++j)); do + (( d[j] = j, b[j] = 2 )) +done + +for ((i = 1; i <= m; ++i)); do + (( p = ${d[(i-1)*w]} )) + (( q = ${d[i*w]} )) + for ((j = 1; j <= n; ++j)); do + if [[ ${s[i-1]} == ${t[j-1]} ]]; then + (( z = p, e = 3 )) + else + (( z = p + 1, e = 7 )) + fi + (( y = q + 1 )) + # Half the time is spent executing the next line. + (( x = (p = ${d[(i-1)*w+j]}) + 1 )) + if (( x <= y )); then + if (( x <= z )); then + (( d[i*w+j] = q = x, b[i*w+j] = 1 )) + else + (( d[i*w+j] = q = z, b[i*w+j] = e )) + fi + elif (( y <= z )); then + (( d[i*w+j] = q = y, b[i*w+j] = 2 )) + else + (( d[i*w+j] = q = z, b[i*w+j] = e )) + fi + done +done + +local -i k kind=-1 s1=m s2=m t1=n t2=n +local res=() +(( i = m, j = n )) +while (( i || j )); do + if (( (k = b[i*w+j]) != kind )); then + if (( s2 > s1 || t2 > t1 )); then + res+=("${t:$t1:$((t2-t1))}" "${s:$s1:$((s2-s1))}") + (( s1 = s2 = i, t1 = t2 = j )) + fi + (( kind = k )) + fi + (( i -= k & 1, s1 -= k & 1, j -= (k >> 1 ) & 1, t1 -= (k >> 1 ) & 1 )) +done + +if (( s2 > s1 || t2 > t1 )); then + res+=("${t:$t1:$((t2-t1))}" "${s:$s1:$((s2-s1))}") +fi + +typeset -g reply=("${(Oa)res[@]}") diff --git a/fn/-z4h-tmux-bypass b/fn/-z4h-tmux-bypass new file mode 100644 index 0000000..0e1281c --- /dev/null +++ b/fn/-z4h-tmux-bypass @@ -0,0 +1,12 @@ +#!/usr/bin/env zsh + +local x +builtin printf -v x "$@" + +(( _z4h_can_save_restore_screen )) && x+=$'\ePtmux;'${x//$'\e'/$'\e\e'}$'\e\\' + +if [[ -t 1 ]]; then + print -rn -- "$x" +elif [[ -v _z4h_tty_fd ]]; then + print -rnu $_z4h_tty_fd -- "$x" +fi diff --git a/fn/-z4h-update-dir-history b/fn/-z4h-update-dir-history new file mode 100644 index 0000000..bdfe19d --- /dev/null +++ b/fn/-z4h-update-dir-history @@ -0,0 +1,52 @@ +#!/usr/bin/env zsh + +if (( ${+_z4h_dir_hist_fd} )); then + zle -F "$_z4h_dir_hist_fd" + exec {_z4h_dir_hist_fd}>&- + unset _z4h_dir_hist_fd +fi + +local dir +zstyle -s :z4h:dir-history: cwd dir || dir=${(%):-%~} +[[ -z $dir || $dir == ${_z4h_last_dir-} ]] && return + +eval "$_z4h_opt" + +typeset -g _z4h_last_dir=$dir +[[ $dir != ('~'|/)* ]] && return + +-z4h-read-dir-history || return + +if (( ! $#dirstack && (DIRSTACKSIZE || ! $+DIRSTACKSIZE) )); then + local d stack=() + for d in $_z4h_dir_history; do + { + if [[ ($#stack -ne 0 || $d != $dir) ]]; then + d=${~d} + if [[ -d ${d::=${(g:oce:)d}} ]]; then + stack+=($d) + (( $+DIRSTACKSIZE && $#stack >= DIRSTACKSIZE - 1 )) && break + fi + fi + } always { + TRY_BLOCK_ERROR=0 + } + done 2>/dev/null + dirstack=($stack) +fi + +local -i pos=$_z4h_dir_history[(ie)$dir] +_z4h_dir_history[pos]=() +_z4h_dir_history[1,0]=($dir) + +local max_size +zstyle -s :z4h:dir-history: max-size max_size +if [[ $max_size != -<-> ]]; then + [[ $max_size == <-> ]] || max_size=10000 + local -i drop=$(($#_z4h_dir_history - max_size)) + if (( drop > 0 )); then + _z4h_dir_history[-drop,-1]=() + fi +fi + +-z4h-write-dir-history diff --git a/fn/-z4h-vte-osc7 b/fn/-z4h-vte-osc7 new file mode 100644 index 0000000..83bf3fa --- /dev/null +++ b/fn/-z4h-vte-osc7 @@ -0,0 +1,26 @@ +#!/usr/bin/env zsh +# +# The original __vte_osc7 looks like this: +# +# __vte_osc7 () { +# printf "\033]7;file://%s%s\033\\" "${HOSTNAME}" "$(/usr/libexec/vte-urlencode-cwd)" +# } +# +# It runs on precmd. + +emulate -L zsh -o no_multi_byte -o extended_glob + +if [[ $PWD == /* && $PWD -ef . ]]; then + local cwd=$PWD +else + local cwd=${${:-.}:a} +fi +local host=$HOST +[[ -n $cwd && $host != *$'\e'* ]] || return + +if [[ $cwd != $_z4h_vte_last_cwd ]]; then + local MATCH MBEGIN MEND + typeset -g _z4h_vte_last_encoded_cwd=${cwd//(#m)[^a-zA-Z0-9"\/:_.-!'()~"]/%${(l:2::0:)$(([##16]#MATCH))}} +fi + +-z4h-tmux-bypass '\e]7;file://%s%s\e\' "$host" "$_z4h_vte_last_encoded_cwd" diff --git a/fn/-z4h-welcome b/fn/-z4h-welcome new file mode 100644 index 0000000..31d6ba2 --- /dev/null +++ b/fn/-z4h-welcome @@ -0,0 +1,96 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +add-zsh-hook -d -- precmd -z4h-welcome + +[[ -e $Z4H/welcome && -e $POWERLEVEL9K_CONFIG_FILE ]] || return 0 + +local backup_dir +backup_dir=$(<$Z4H/welcome) || return +zf_rm -f -- $Z4H/welcome + +() { + [[ $POWERLEVEL9K_CONFIG_FILE != ~/.p10k.zsh ]] && return + [[ $POWERLEVEL9K_ICON_PADDING == none ]] || return + [[ $POWERLEVEL9K_MODE == nerdfont-complete ]] && return + [[ $POWERLEVEL9K_MODE == ascii ]] || return + local cfg + cfg=$(<~/.p10k.zsh) || return + [[ $cfg == *'nerdfont-complete + powerline'* ]] || return +} +local -i suggest_font=$? + +local bash_rcs=(~/.profile(N) ~/.bash_profile(N) ~/.bashrc(N)) + +if [[ ! -t 2 ]]; then + (( $+functions[p10k] )) && p10k clear-instant-prompt +fi + +{ + print + -z4h-flowing -i0 -- %F{3}Zsh For Humans%f installed successfully! + print + -z4h-flowing -i0 -- Next steps: + print + -z4h-flowing -i4 -- ' - Your' new personal Zsh config is in %U~/.zshrc%u. Edit this file to \ + export environment variables, define aliases, etc. There are plenty of \ + examples and comments to get you started. + print + if [[ -e $backup_dir && $backup_dir == ~/zsh-backup/[0-9.-]## ]]; then + -z4h-flowing -i4 -- ' - Your' previous Zsh config files are in \ + %U~/zsh-backup/${backup_dir:t}%u. They are no longer read when you start \ + %2Fzsh%f. You might want to copy bits and pieces from them to the new \ + %U~/.zshrc%u. + print + fi + if [[ /$_z4h_orig_shell == */bash ]] && (( $+commands[bash] && $#bash_rcs )); then + -z4h-flowing -i4 -- ' - 'Zsh does not read startup files used by Bash. You might want to copy \ + bits and pieces from them to %U~/.zshrc%u. Here are the files: + print + local rc + for rc in $bash_rcs; do + -z4h-flowing -i4 -- ' '%U~/${${rc:t}//\%/%%}%u + done + print + fi + if [[ -n $TMUX && $TERM == screen && ! -e ~/.tmux.conf ]]; then + -z4h-flowing -i4 -- ' - You' are using %2Ftmux%f but don\'t have %U~/.tmux.conf%u. This is \ + limiting the number of colors your terminal can display. You might want \ + to create this file and restart Zsh to enable more colors: + print + -z4h-flowing -i4 -- " %3F>%f%U~/.tmux.conf%u %3F<<<'set -g default-terminal screen-256color'%f" + -z4h-flowing -i4 -- ' TERM=screen-256color %U%2Fexec%u%f %2Fzsh%f' + print + -z4h-flowing -i4 -- ' You' will see %4FPowerlevel10k%f wizard once again, this time with \ + more choices. + print + fi + if (( suggest_font )); then + -z4h-flowing -i4 -- ' - Install' the recommended font from %4FPowerlevel10k%f to enable \ + additional glyphs in the terminal: + print + local url=https://github.com/romkatv/powerlevel10k/blob/master/font.md + url=${${url//\%/%%}//\\/\\\\} + if (( _p9k_term_has_href )); then + url='%{\e]8;;'$url'\a%}'$url'%{\e]8;;\a%}' + fi + -z4h-flowing -i4 -- ' '$url + print + -z4h-flowing -i4 -- ' Then' choose a new prompt style through the %4FPowerlevel10k%f wizard: + print + -z4h-flowing -i4 -- ' '%2Fp10k%f %Bconfigure%b + print + else + -z4h-flowing -i4 -- ' - Prompt' config from %4FPowerlevel10k%f is in \ + %U~/${${POWERLEVEL9K_CONFIG_FILE:t}//\%/%%}%u. To customize prompt, you \ + can either manually edit this file or generate a new version through the \ + wizard: + print + -z4h-flowing -i4 -- ' '%2Fp10k%f %Bconfigure%b + print + fi + + -z4h-flowing -i0 -- Enjoy %F{3}Zsh For Humans%f! + print +} >&2 diff --git a/fn/-z4h-with-local-history b/fn/-z4h-with-local-history new file mode 100644 index 0000000..6cff31c --- /dev/null +++ b/fn/-z4h-with-local-history @@ -0,0 +1,13 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +local last=$LASTWIDGET +zle .set-local-history -n $1 +shift +{ + () { local -h LASTWIDGET=$last; "$@" } "$@" +} always { + zle .set-local-history -n 0 +} +return 0 diff --git a/fn/-z4h-write-dir-history b/fn/-z4h-write-dir-history new file mode 100644 index 0000000..5f5504e --- /dev/null +++ b/fn/-z4h-write-dir-history @@ -0,0 +1,6 @@ +#!/usr/bin/env zsh + +local file=$Z4H/stickycache/dir-history-$EUID +local tmp=$file.tmp.$$ +print -rC1 -- $_z4h_dir_history >$tmp +zf_mv -f -- $tmp $file diff --git a/fn/-z4h-zle-line-finish b/fn/-z4h-zle-line-finish new file mode 100644 index 0000000..e0ce6ce --- /dev/null +++ b/fn/-z4h-zle-line-finish @@ -0,0 +1,26 @@ +#!/usr/bin/env zsh + +local -i ret + +if [[ -v widgets[-z4h-orig-zle-line-finish] ]]; then + zle -- -z4h-orig-zle-line-finish "$@" || ret=$? +fi + +if [[ -v _z4h_redraw_fd ]]; then + zle -F "$_z4h_redraw_fd" + exec {_z4h_redraw_fd}>&- + unset _z4h_redraw_fd +fi + +if [[ $BUFFER == *' ' ]] && + ! zstyle -t :z4h:history preserve-trailing-whitespace && + -z4h-is-valid-list "$PREBUFFER$BUFFER"; then + BUFFER=${BUFFER%% #} +fi + +unset POSTDISPLAY _z4h_autosuggest_buffer _z4h_autosuggestion +if [[ -v region_highlight ]]; then + _zsh_highlight +fi + +return ret diff --git a/fn/-z4h-zle-line-init b/fn/-z4h-zle-line-init new file mode 100644 index 0000000..509232f --- /dev/null +++ b/fn/-z4h-zle-line-init @@ -0,0 +1,39 @@ +#!/usr/bin/env zsh + +local -i ret + +if [[ ${CONTEXT-} != start || -v _z4h_transient_rprompt ]]; then + if [[ ${CONTEXT} == start ]]; then + unsetopt transient_rprompt + unset _z4h_transient_rprompt + elif [[ ! -o transient_rprompt ]]; then + setopt transient_rprompt + typeset -g _z4h_transient_rprompt= + fi +fi + +if (( ${+widgets[-z4h-orig-zle-line-init]} )); then + zle -- -z4h-orig-zle-line-init "$@" || ret=$? +fi + +if (( ${+_z4h_redraw_fd} )); then + zle -F "$_z4h_redraw_fd" + exec {_z4h_redraw_fd}>&- + unset _z4h_redraw_fd +fi + +unset POSTDISPLAY _z4h_autosuggest_buffer _z4h_autosuggestion _zsh_highlight__highlighter_${^ZSH_HIGHLIGHT_HIGHLIGHTERS}_cache +typeset -g _ZSH_HIGHLIGHT_PRIOR_BUFFER= +typeset -gi _ZSH_HIGHLIGHT_PRIOR_CURSOR=CURSOR +region_highlight=() + +if [[ ${(%):-%~} != ${_z4h_last_dir-} && ! -v _z4h_dir_hist_fd ]]; then + typeset -gi _z4h_dir_hist_fd + if sysopen -o cloexec -ru _z4h_dir_hist_fd /dev/null; then + zle -F $_z4h_dir_hist_fd -z4h-update-dir-history + else + unset _z4h_dir_hist_fd + fi +fi + +return ret diff --git a/fn/-z4h-zle-line-pre-redraw b/fn/-z4h-zle-line-pre-redraw new file mode 100644 index 0000000..ad710e0 --- /dev/null +++ b/fn/-z4h-zle-line-pre-redraw @@ -0,0 +1,40 @@ +#!/usr/bin/env zsh + +local -i ret + +if (( ${+widgets[-z4h-orig-zle-line-pre-redraw]} )); then + zle -- -z4h-orig-zle-line-pre-redraw "$@" || ret=$? +fi + +if (( ${+_z4h_substring_search_highlight} )) && + [[ ${_history_substring_search_cursor-} != $CURSOR || + ${_history_substring_search_result-} != $BUFFER ]]; then + typeset -g _history_substring_search_result= + typeset -g _history_substring_search_query_highlight= + unset _z4h_substring_search_highlight _history_substring_search_cursor +fi + +if (( PENDING || KEYS_QUEUED_COUNT )); then + if (( ! ${+_z4h_redraw_fd} )); then + typeset -gi _z4h_redraw_fd + if sysopen -o cloexec -ru _z4h_redraw_fd /dev/null; then + zle -F $_z4h_redraw_fd -z4h-redraw-buffer + else + unset _z4h_redraw_fd + fi + fi + if [[ -n "${POSTDISPLAY-}" && + "${#BUFFER}" -ne "${#_z4h_autosuggest_buffer}" && + -v region_highlight ]]; then + POSTDISPLAY=${_z4h_autosuggestion:${#BUFFER}} + if [[ -n "$POSTDISPLAY" ]]; then + region_highlight[-1]="${#BUFFER} $((${#BUFFER} + ${#POSTDISPLAY})) $ZSH_AUTOSUGGEST_HIGHLIGHT_STYLE" + else + unset POSTDISPLAY _z4h_autosuggest_buffer _z4h_autosuggestion + fi + fi +else + -z4h-redraw-buffer +fi + +return ret diff --git a/fn/_z4h_cursor_max b/fn/_z4h_cursor_max new file mode 100644 index 0000000..57e1f08 --- /dev/null +++ b/fn/_z4h_cursor_max @@ -0,0 +1,9 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" + +if [[ $KEYMAP == vicmd && -n ${BUFFER##*$'\n'} ]]; then + return $(( ${#BUFFER} - 1 )) +else + return ${#BUFFER} +fi diff --git a/fn/_z4h_err b/fn/_z4h_err new file mode 100644 index 0000000..1514f2f --- /dev/null +++ b/fn/_z4h_err @@ -0,0 +1,8 @@ +#!/usr/bin/env zsh + +local -i ret=$? +eval "$_z4h_opt" +print -Pru2 -- '%F{3}--- stack trace (most recent call first) ---%f' +print -lru2 -- "${funcfiletrace[@]}" +print -Pru2 -- '%F{3}--- end of stack trace ---%f' +return $(( ret ? ret : 1 )) diff --git a/fn/bracketed-paste-magic b/fn/bracketed-paste-magic new file mode 100644 index 0000000..fb591d3 --- /dev/null +++ b/fn/bracketed-paste-magic @@ -0,0 +1,232 @@ +# This is a copy of +# https://github.com/zsh-users/zsh/blob/530d6337e69941a5cd802fa7808aae8f33ab1fe9/Functions/Zle/bracketed-paste-magic. +# +# License: +# https://github.com/zsh-users/zsh/blob/530d6337e69941a5cd802fa7808aae8f33ab1fe9/LICENCE +# +# There is a modification at the bottom marked by "zsh4humans" in comments. + +# Starting with zsh-5.1, ZLE began to recognize the "bracketed paste" +# capability of terminal emulators, that is, the sequences $'\e[200~' to +# start a paste and $'\e[201~' to indicate the end of the pasted text. +# Pastes are handled by the bracketed-paste widget and insert literally +# into the editor buffer rather than being interpreted as keystrokes. +# +# This disables some common usages where the self-insert widget has been +# replaced in order to accomplish some extra processing. An example is +# the contributed url-quote-magic widget. The bracketed-paste-magic +# widget replaces bracketed-paste with a wrapper that re-enables these +# self-insert actions, and other actions as selected by the zstyles +# described below. +# +# Setup: +# autoload -Uz bracketed-paste-magic +# zle -N bracketed-paste bracketed-paste-magic + +# The following zstyles may be set to control processing of pasted text. +# +# active-widgets +# Looked up in the context :bracketed-paste-magic to obtain a list of +# patterns that match widget names that should be activated during the +# paste. All other key sequences are processed as "zle .self-insert". +# The default is 'self-*' so any user-defined widgets named with that +# prefix are active along with the builtin self-insert. If this style is +# not set (note: it must be explicitly deleted after loading this +# function, otherwise it becomes set by default) or has no value, no +# widgets are active and the pasted text is inserted literally. If the +# value includes undefined-key, any unknown sequences are discarded from +# the pasted text. +# +# inactive-keys +# This is the inverse of active-widgets, it lists key sequences that +# always use "zle .self-insert" even when bound to an active-widget. +# Note that this is a list of literal key sequences, not patterns. +# This style is in context :bracketed-paste-magic and has no default. +# +# paste-init +# paste-finish +# Also looked up in the context :bracketed-paste-magic, these styles +# each are a list of function names. They are executed in widget +# context but are called as functions (NOT as widgets with "zle name"). +# As with hooks, the functions are called in order until one of them +# returns a nonzero exit status. The parameter PASTED contains the +# current state of the pasted text, other ZLE parameters are as usual. +# Although a nonzero status stops each list of functions, it does NOT +# abort the entire paste operation; use "zle send-break" for that. + +# IMPORTANT: During processing of the paste (after paste-init and +# before paste-finish), BUFFER starts empty and history is restricted, +# so cursor motions etc. may not pass outside of the pasted content. +# However, the paste-init functions have access to the full history and +# the original BUFFER, so they may for example move words from BUFFER +# into PASTED to make those words visible to the active-widgets. + +# Establish default values for styles, but only if not already set +zstyle -m :bracketed-paste-magic active-widgets '*' || + zstyle ':bracketed-paste-magic' active-widgets 'self-*' + +# Helper/example paste-init for exposing a word prefix inside PASTED. +# Useful with url-quote-magic if you have http://... on the line and +# are pasting additional text on the end of the URL. +# +# Usage: +# zstyle :bracketed-paste-magic paste-init backward-extend-paste +# +# TODO: rewrite this using match-words-by-style +# +backward-extend-paste() { + emulate -L zsh + integer bep_mark=$MARK bep_region=$REGION_ACTIVE + if (( REGION_ACTIVE && MARK < CURSOR )); then + zle .exchange-point-and-mark + fi + if (( CURSOR )); then + local -a bep_words=( ${(z)LBUFFER} ) + if [[ -n $bep_words[-1] && $LBUFFER = *$bep_words[-1] ]]; then + PASTED=$bep_words[-1]$PASTED + LBUFFER=${LBUFFER%${bep_words[-1]}} + fi + fi + if (( MARK > bep_mark )); then + zle .exchange-point-and-mark + fi + REGION_ACTIVE=$bep_region +} + +# Example paste-finish for quoting the pasted text. +# +# Usage e.g.: +# zstyle :bracketed-paste-magic paste-finish quote-paste +# zstyle :bracketed-paste-magic:finish quote-style qqq +# +# Using "zstyle -e" to examine $PASTED lets you choose different quotes +# depending on context. +# +# To forcibly turn off numeric prefix quoting, use e.g.: +# zstyle :bracketed-paste-magic:finish quote-style none +# +quote-paste() { + emulate -L zsh + local qstyle + # If there's a quoting style, be sure .bracketed-paste leaves it alone + zstyle -s :bracketed-paste-magic:finish quote-style qstyle && NUMERIC=1 + case $qstyle in + (b) PASTED=${(b)PASTED};; + (q-) PASTED=${(q-)PASTED};; + (\\|q) PASTED=${(q)PASTED};; + (\'|qq) PASTED=${(qq)PASTED};; + (\"|qqq) PASTED=${(qqq)PASTED};; + (\$|qqqq) PASTED=${(qqqq)PASTED};; + (Q) PASTED=${(Q)PASTED};; + esac +} + +# Now the actual function + +bracketed-paste-magic() { + if [[ "$LASTWIDGET" = *vi-set-buffer ]]; then + # Fast exit in the vi-mode cut-buffer context + zle .bracketed-paste + return + else + # Capture the pasted text in $PASTED + local PASTED REPLY + zle .bracketed-paste PASTED + fi + + # Really necessary to go to this much effort? + local bpm_emulate="$(emulate)" bpm_opts="$-" + + emulate -L zsh + local -a bpm_hooks bpm_inactive + local bpm_func bpm_active bpm_keymap=$KEYMAP + + # Run the paste-init functions + if zstyle -a :bracketed-paste-magic paste-init bpm_hooks; then + for bpm_func in $bpm_hooks; do + if (( $+functions[$bpm_func] )); then + function () { + emulate -L $bpm_emulate; set -$bpm_opts + $bpm_func || break + } + fi + done + fi + + zstyle -a :bracketed-paste-magic inactive-keys bpm_inactive + if zstyle -s :bracketed-paste-magic active-widgets bpm_active '|'; then + # Save context, create a clean slate for the paste + integer bpm_mark=$MARK bpm_region=$REGION_ACTIVE + integer bpm_numeric=${NUMERIC:-1} + integer bpm_limit=$UNDO_LIMIT_NO bpm_undo=$UNDO_CHANGE_NO + zle .split-undo + UNDO_LIMIT_NO=$UNDO_CHANGE_NO + BUFFER= + CURSOR=1 + fc -p -a /dev/null 0 0 + if [[ $bmp_keymap = vicmd ]]; then + zle -K viins + fi + + # There are active widgets. Reprocess $PASTED as keystrokes. + NUMERIC=1 + zle -U - "$PASTED" + + # Just in case there are active undo widgets + + while [[ -n $PASTED ]] && zle .read-command; do + PASTED=${PASTED#$KEYS} + if [[ $KEYS = ${(~j:|:)${(b)bpm_inactive}} ]]; then + zle .self-insert + else + case $REPLY in + (${~bpm_active}) function () { + emulate -L $bpm_emulate; set -$bpm_opts + zle $REPLY -w + };; + (*) zle .self-insert;; + esac + fi + done + PASTED=$BUFFER + + # Restore state + zle -K $bpm_keymap + fc -P + MARK=$bpm_mark + REGION_ACTIVE=$bpm_region + NUMERIC=$bpm_numeric + zle .undo $bpm_undo + UNDO_LIMIT_NO=$bpm_limit + fi + + # PASTED has been updated, run the paste-finish functions + if zstyle -a :bracketed-paste-magic paste-finish bpm_hooks; then + for bpm_func in $bpm_hooks; do + if (( $+functions[$bpm_func] )); then + function () { + emulate -L $bpm_emulate; set -$bpm_opts + $bpm_func || break + } + fi + done + fi + + # Reprocess $PASTED as an actual paste this time + zle -U - $PASTED$'\e[201~' # append paste-end marker + zle .bracketed-paste -- "$@" + zle .split-undo + + # zsh4humans patch: disable redisplay as it breaks highlighting. + # + # # Arrange to display highlighting if necessary + # if [[ -z $zle_highlight || -n ${(M)zle_highlight:#paste:*} ]]; then + # zle -R + # zle .read-command && zle -U - "$KEYS" + # fi +} + +# Handle zsh autoloading conventions +if [[ "$zsh_eval_context" = *loadautofunc && ! -o kshautoload ]]; then + bracketed-paste-magic "$@" +fi diff --git a/fn/notes.md b/fn/notes.md new file mode 100644 index 0000000..8ae5151 --- /dev/null +++ b/fn/notes.md @@ -0,0 +1,1211 @@ +Write man pages: `man z4h`, `man z4hssh`, etc. + +--- + +Implement completions for `z4h`. + +--- + +Make `z4h help` more useful. + +--- + +Implement `z4h bindkey`. `z4h bindkey -l` should list bindings in a nice table with sections. All +widgets should have a human-readable description. + +`z4h bindkey -L [array]` should list all bindings in `z4h bindkey` command format. If `array` is +specified, it's filled with `z4h bindkey` commands that can be passed through `eval`. `-r escseq` +should allow binding raw escape sequences. By default should warn when something got unbound. Can be +turned off with `-q`. + +```zsh +zh4 bindkey emacs,viins 'up','ctrl-p' z4h-up-local-history +``` + +--- + +`vicmd` has `^P` bound to `up-history` by default. It should probably use bound to the same thing +but with local history. + +`vicmd` has no bindings for global history. It should. + +--- + +Add "Try it locally" to docs. Basically, create a directory and make it `ZDOTDIR`. + +--- + +Add `z4h replicate [-f] [-b dir] [var=val]...`. It should have its own `:z4h:replicate: files` +style. Argument `ZDOTDIR=$HOME` is implied. All zsh startup files that don't exist in the current +`ZDOTDIR` should also not exist in the target. `-b ''` disables backup. The default argument is +something like `$ZDOTDIR/z4h-backup.date-time`. Files should be stored there with subdirectories all +the way from `/`. `-f` causes silent action, including backing up or deleting files. If not set, +`z4h replicate` first prints the whole plan of what it's going to do (move this file here, copy that +file there, etc.) and asks for confirmation. + +--- + +Add `z4h uninstall`. + +--- + +Add `install` script that people can download and run. Should have a wizard inside. + +--- + +Try to make this work: + +```zsh +zstyle ':completion:*:manuals' separate-sections true +zstyle ':completion:*:manuals.*' insert-sections true +``` + +`man printf` should show: + +```text +1 printf -- general commands +3 printf -- library functions +3p printf -- library functions [POSIX] +``` + +Maybe this can be done by writing a custom completion function for `man` and enabling it with +`compdef`. + +--- + +Install `bat`. + +--- + +Add options to `z4h chsh`. Perhaps `-v` so that it prints what your login and current shells are +and what it's going to do about it. Also add `-r` or something to prevent the creation of +`no-chsh`. Maybe `-f` to ignore `no-chsh` and to call `chsh` even if it seems unnecessary. +Should return `0` if login shell is current shell or if it successfully changes it. Should return +`1` if user says "no" and `2` on errors. + +--- + +Move `z4h chsh` to `z4h-chsh`. + +--- + +`zcompile` autoloadable files. + +--- + +Create `fn` directory similarly to `bin`. Add it to `fpath`. + +--- + +Add `:z4h:fzf disable yes`. List: `fzf`, `fzf-bin`, `fzf-tab`, `powerlevel10k`, `extra-completions`, +`autosuggestions`, `syntax-highlighting`. This disabled cloning and all usage. + +--- + +Add `:z4h:fzf update no`. The list is the same as for `disable` plus `z4h`. + +--- + +Add `:z4h:fzf git-clone-flags` and `:z4h:fzf git-pull-flags`. + +--- + +Add `-f` to `z4h clone` to force `Z4H_UPDATE=1` behavior. + +--- + +Add `z4h download [-f] [-q] [-o file] url` that uses `curl` or `wget`. Respects `Z4H_UPDATE` unless +`-f` is specified. + +--- + +Add `z4h-recovery-shell`. Copy from https://github.com/zsh4humans/core/blob/master/init.zsh. Bind +it to something. Defend against builtins being overridden by functions or disabled. + +--- + +Add `_z4h_intro`. Copy from https://github.com/zsh4humans/core/blob/master/init.zsh. + +--- + +Add `_z4h_err`. Copy from https://github.com/zsh4humans/core/blob/master/init.zsh. + +--- + +Add this: + +```zsh +zstyle :z4h: hidden-files [ignore|show|recurse] +``` + +If not `ignore`, should set `dotglob`, add `-A` to `ls`, make `alt-down` and `alt-f` show hidden +leaves. `recurse` should make `alt-down` and `alt-f` recurse hidden directories. + +Will need to find another example of modifying aliases in `.zshrc` (currently it adds `-A` to `ls`). + +--- + +Make `cd ` consistent with `alt-down`. + +--- + +Check if `alt-f` works on alpine (busybox). + +--- + +Add options I like directly to `.zshrc`. They'll also serve as example. (Not sure if there are any +that aren't already set in `z4h.zsh`.) + +--- + +Check what happens when running `sudo zsh`. Consider adding `$USER` to `$Z4H`. + +--- + +Name all private functions like this: `-z4h-foo-bar`. + +--- + +Make `-z4h-clone` (or rather `-z4h-clone`) autoloadable. + +--- + +Replace `git pull` with `git fetch origin $ref` followed by `git reset --hard origin/$ref`. If the +latter fails, try `git clean -df` and repeat `git reset`. If the whole thing fails, try `git clone`. + +--- + +`-z4h-clone` should print "installing" or "updating" based on the presence of the target directory. +It shouldn't produce additional output on success. + +--- + +Restore "downloading z4h.zsh" message in `.zshrc`. + +--- + +Change the structure of `$Z4H` to this: + +```text +. +├── bin +├── romkatv +│   └── zsh4humans +│   ├── fn +│   │   ├── -z4h-clone +│   │   └── z4h-help +│   ├── main.zsh # defines and calls _z4h_prelude, defines z4h, etc. +│   └── z4h.zsh # the same as $Z4H/z4h.zsh but could be newer version; not used +├── zsh-users +│   └── zsh-autosuggestions +└── z4h.zsh # downloaded by zshrc +``` + +`z4h.zsh` is in fact a pure POSIX sh script. It looks like this: + +```sh +if [ -e "$Z4H"/zsh4humans/main.zsh ]; then + . "$Z4H"/zsh4humans/main.zsh + return +fi + +# git clone, curl or wget romkatv/zsh4humans + +. "$Z4H"/zsh4humans/main.zsh +``` + +`romkatv/zsh4humans` shouldn't be hard-coded but derived from `$Z4H_URL`. + +It's not very important to update this file. The only case where it executes to the end after the +initial installation is when `z4h` self-update gets aborted in the very short time window where +`romkatv/zsh4humans` is renamed. So it's OK to never update the root `z4h.zsh`. But updating it +is also OK (`cp` + `mv` should be easy enough). + +--- + +Use src branch of powerlevel10k. When updating, use the following algorithm: + +- if there is more than one `gitstatus/usrbin/gitstatusd-*`, nuke them all +- rename `gitstatus/usrbin/gitstatusd-*` +- `git pull` +- rename `gitstatus/usrbin/gitstatusd-*` back +- run `gitstatus/install` + +When initializing, check if there is exactly one `gitstatus/usrbin/gitstatusd-*`. If not, nuke +all and run `gitstatus/install`. + +This way `gitstatus/install` is called only when installing or updating, so it's ok if it runs +`uname` (by the way, change `gitstatus` to use `uname -sm`). We also avoid downloading `gitstatusd` +when it doesn't change. + +--- + +Try harder when looking for zsh. Check `command zsh`, `$SHELL`, `/usr/local/bin/zsh`, +`/usr/bin/zsh`, `/bin/zsh` and `~/.zsh-bin/bin/zsh`, in this order. + +--- + +Add `-z4h-restart` (or `z4h restart`?) and use it instead of plain `exec $_z4h_exe`. This function +should check whether `$_z4h_exe` is good before execing it. + +```zsh +$_z4h_exe -fc '[[ $ZSH_VERSION == (5.<4->*|<6->.*) ]]' +``` + +Maybe also check that `zmodload zsh/zselect` and `autoload add-zsh-hook` work. + +If `$_z4h_exe` is not good, try to find a good one. If there aren't any, install zsh-bin. If +everything fails, keep current prompt. Basically the same thing as `_z4h_prelude`. + +--- + +Use some kind of counter to detect exec loop during initialization. + +--- + +Figure out how to allow customization of zsh-bin installation location. This should be defined with +`zstyle`, so it won't be available when we actually need to install zsh-bin. Install it to `$Z4H`, +`exec` into zsh, and then move zsh-bin to its intended location. + +Should it be allowed to put zsh-bin in `$Z4H`? One problem with this is that `chsh` becomes very +dangerous. OK in ssh though? Probably better to disallow putting zsh-bin under `$XDG_CACHE_HOME` +or `~/.cache`. + +```zsh +zstyle :z4h: zsh-installation-dir /usr/local ~/.local +zstyle :z4h:ssh zsh-installation-dir /usr/local ~/.local +``` + +If there is more than one option, ask the user to choose. Mark options that would require `sudo`. + +--- + +Make `persist=1` option in `zstyle :z4h:ssh files` the default and remove support for `persist`. + +--- + +Make `LS_COLORS` less obnoxious on NTFS. + +--- + +When `main.zsh` is being sourced, traverse the stack to find `.zshrc` and export `ZDOTDIR` pointing +to its directory. + +--- + +Remove all uses of `$TTY` before `z4h init`. Instead, check `[[ -t 0 && -t 1 ]]`. + +--- + +Add this: + +```zsh +zstyle :z4h:locale lang 'c' 'en_us' 'en_gb' 'en_*' '*' +zstyle :z4h:locale force no # if 'no', locale is changed only when encoding is not UTF-8 +``` + +Set locale early in `z4h install`. + +--- + +Add this: + +```zsh +zstyle :z4h:fzf-tab channel stable +zstyle :z4h:syntax-highlighting channel dev +zstyle :z4h:powerlevel10k git-ref src + +zstyle :z4h:fzf-tab:channel stable fca05e66d1c397cb5e72e8b185b1c3d1a0fc063d +zstyle :z4h:fzf-tab:channel dev master +``` + +If `git-ref` is set, it wins. Otherwise commit is derived from `channel`. + +--- + +Remove `~/.zshrc` from master branch. + +--- + +Add this: + +```zsh +z4h add-hook --after '*' --before 'foo*' --after 'foobar' preinit powerlevel10k _z4h-powerlevel10k-init arg1 arg2 + +z4h run-hooks preinit arg3 arg4 + +z4h preinit +``` + +- Ordering constraints are applied in the order they are specified. +- `z4h preinit` runs `z4h run-hooks preinit`. It complains if executed for the second time. +- The precmd hook runs `z4h postinit` if it hasn't run yet. This allows users to run it manually. +- If `_z4h_powerlevel10k_init arg1 arg2` is not specified, it defaults to `powerlevel10k`. +- It's OK to use `-a` and `-b` instead of `--after` and `--before`. + +`.zshrc` will look like this: + +```zsh +. "$Z4H"/z4h.zsh || return + +z4h use zsh-syntax-highlighting +z4h use powerlevel10k + +z4h install # installs and/or updates everything +z4h preinit # enables instant prompt, sources most things, sets parameters, etc. +z4h postinit # runs compinit and sources zsh-syntax-highlighting; called from precmd if not called called explicitly +``` + +`z4h use foo` calls `z4h-use-foo`, which in turn calls `z4h add-hook` a few times and nothing else. + +--- + +Add this: + +```zsh +if z4h use -t powerlevel10k; then # is powerlevel10k used? + ... +fi +``` + +--- + +Add config presets: + +```zsh +. "$Z4H"/z4h.zsh || return + +zstyle :z4h: config-version 1.0.0 # <== + +z4h use zsh-syntax-highlighting +z4h use powerlevel10k +... + +``` + +`config-version` can be used by the core code to do things differently but its primary purpose is +to set default values of various parameters, styles, options, bindings, etc. + +All `z4h use` directives should be in `.zshrc`. `z4h install` and `z4h preinit` should also be in +`.zshrc`. Everything else should be in preset. + +--- + +Make `z4h ssh` use the same directories (`ZDOTDIR`, `Z4H`, etc.) as local. + +Make setup and teardown configurable through `zstyle`. + +```zsh +zstyle ':z4h:ssh:*' setup my-ssh-setup + +function my-ssh-setup() { + local ssh_args=("$@") + z4h ssh-send-env FOO $foo + z4h ssh-send-env BAR + z4h ssh-eval 'baz=qux' + z4h ssh-send-file -f /foo/bar '$FOO/baz' +} +``` + +All these commands must be applied in the order they are listed. Remote code must be interpreted +by zsh (hence `$FOO/baz` is OK without quotes). + +`ssh-send-file` should be able to send directories. The meaning of trailing slash in source and +`destination` should be the same as in `rsync`. + +--- + +`z4h ssh` should be able to send history and then to retrieve it. For retrieving there needs to +be `teardown` hook similar to `setup`. + +--- + +`z4h ssh` should perform setup and interactive connection with these SSH options: + +``` +-o 'ControlMaster auto' -o 'ControlPath ~/.ssh/control-master-%r@%h:%p' -o 'ControlPersist 10' +``` + +These can be overridden via zstyle. + +--- + +Create `zle-experimental-save-restore-cursor` branch in `zsh`. Sync it to 5.8 and add `fix-sigwinch` +code on top. Guard the new code with `ZLE_EXPERIMENTAL_SAVE_RESTORE_CURSOR`. + +Create a patch from this commit and store it in `zsh-bin`. Modify `build` to apply the patch. +Set patchlevel to the commit hash from `zle-experimental-save-restore-cursor`. + +Add `ZLE_EXPERIMENTAL_SAVE_RESTORE_CURSOR=1` to zsh4humans. + +--- + +Add an option to specify the minimum required zsh version. It should also allow specifying that you +really want zsh from zsh-bin and not some other zsh 5.8. + +--- + +Add these straight to `.zshrc`? + +```zsh +zstyle ':completion:*' sort false +zstyle ':completion:*' list-dirs-first true +``` + +It would be nice to add `--group-directories-first` to `ls` for consistency but it's tricky because +it's not POSIX. + +--- + +Figure out if `_approximate` matcher works and whether it's worth it. + +--- + +Implement syntax highlighting of preview in `z4h-fzf-history` with `zsh-syntax-highlighting`. To do +this, start a companion `zsh` (like in gitstatus), load `zsh-syntax-highlighting` there (make sure +to disable widget wrapping as it's very slow) and communicate with it over pipes. + +--- + +Replace this status message from `z4h reset`: + +```text +z4h: cloning zsh4humans/powerlevel10k +``` + +With this: + +```text +z4h: cloning romkatv/powerlevel10k +``` + +--- + +Revamp vi bindings. See https://github.com/zsh-vi-more/vi-motions and +https://github.com/softmoth/zsh-vim-mode. + +Cursor shape changes should go to p10k? + +--- + +When looking for zsh, check `/etc/shells`. + +If there are several zsh versions installed, pick the one from `PATH`. If it's too old, pick +the latest latest from the rest. + +--- + +When zsh-bin installs zsh, it should ask whether to add it to /etc/shells. By default it should +do this only when the installation directory is world-readable. + +--- + +If `ZDOTDIR` is set and doesn't point to `$HOME` when `install` starts, ask whether to install to +`$HOME` or `$ZDOTDIR`. Backups should go under the same directory. + +--- + +Create `~/.zshenv` with just `setopt no_global_rcs` in it. + +--- + +Add `z4h use [-d] [-f] [module]...` where `module` is one of the built-in things: +`zsh-users/zsh-autosuggestions`, `bindkey`, `term-title`, etc. + +Without `-d` modules are added to `_z4h_use_queue`. With `-d` they are added to +`_z4h_use_queue_d[-1]`. The latter is an array with nul separated lists as its elements. + +On `-f` it should install a `precmd` hook called `-z4h-precmd-$#_z4h_install_queue_d` that calls +`${(0)_z4h_install_queue_d[${0#-z4h-precmd-}]}`, and call `-z4h-use-rigi $_z4h_use_queue`. + +`-z4h-use-rigi` should be the same as the current `-z4h-init` with a bunch of conditions added in: + +```zsh +if (( ${@[(Ie)zsh-users/zsh-autosuggestions]} )); then + ... +fi +``` + +It should issue warnings (but not fail) for arguments it doesn't recognize. + +--- + +Add this: + +```zsh +zstyle ':z4h:' preset rigi +``` + +Could support multiple values for preset "addons". Presets can be used by the core code to do things +differently but its primary purpose is to set default values of various parameters, styles, options, +bindings, etc. + +`z4h init` will simply call the preset function -- `-z4h-init-rigi`. The latter will do this: + +```zsh +local -a mods=() +zstyle -T :z4h:zsh-users/zsh-autosuggestion install && mods+=zsh-autosuggestion +... +z4h install -f -- $mods + +local -a mods=() +zstyle -T :z4h:compinit use && mods+=compinit +... +z4h use -d -- $mods + +local -a mods=() +zstyle -T :z4h:zsh-users/zsh-autosuggestion use && mods+=zsh-autosuggestion +... +z4h use -f -- $mods +``` + +--- + +Use `fc` to write history in `z4h-stash-buffer`. Might need `fc -p`. + +--- + +Support this for local development: + +``` +zstyle ':z4h:romkatv/powerlevel10k' channel command ln -s ~/powerlevel10k +``` + +The command is called with an extra argument designating target directory. + +--- + +Move the top of `~/.zshrc` to `~/.zshenv`. To make it work, `z4h ssh` will need to start +interactive sh. + +--- + +Move `$ZDOTDIR` to `~/.zsh`, leave just `~/.zshenv` in the home directory. Also leave a symlink +form `~/.zshrc` to `~/.zsh/.zshrc` so that users and bad tools don't get confused. + +--- + +When retrieving files in `z4h ssh` and there is no base64 either on local or remote host, use this +for encoding: + +```zsh +od -t x1 -An -v | tr -d '[:space:]' +``` + +Decode with `sysread`, followed by `print -n -- ${buf//(#m)??/'\x'$MATCH}`. This can be done +while reading from the network to speed things up. + +This encoding has 50% overhead compared to base64. + +--- + +Make history over ssh more robust so that it never gets overwritten. + +--- + +Protect scripts against rogue aliases as functions as described in the EXAMPLES section of +https://pubs.opengroup.org/onlinepubs/9699919799/utilities/command.html. Use `unset -f` on +all builtins, too. + +```zsh +IFS=' +' +'unset' '-f' 'unalias' '[' 'cat' ... +'unalias' '-a' +PATH="$(command -p getconf PATH):$PATH" +... +``` + +This can be broken only by function `unset`. + +There is a similar but different example at +https://pubs.opengroup.org/onlinepubs/9699919799/utilities/env.html. + +--- + +Add `z4h pack` that produces `install-z4h` file. It should be similar to the bootstrap script +created by `z4h ssh`. It should be all ASCII. It should respect `:z4h:pack:tag extra-files` and +similar. `tag` is the value passed via optional `-t tag`. Defaults to emty. This allows one to +define different file sets for different packs. By default the same set of files as sent by ssh +should be packed, plus all history files. + +--- + +Support `fzf` customization via `zstyle`. Add nice support for bindings (including continuous +completion and query insertion via fake actions like `z4h-accept-and-repeat` and +`z4h-accept-query`). Also allow for low level overrides. + +```zsh +zstyle :z4h:expand-or-complete:fzf command my-fzf +zstyle :z4h:expand-or-complete:fzf bindings ctrl-u:kill-line +zstyle :z4h:expand-or-complete:fzf flags --no-exact +``` + +`flags` and `bindings` have the semantics of *extra* flags and bindings. + +Extra flags should be added at the front of standard flags because the first flag wins. +All flags should be passed to the command (`my-fzf` above) where users can munge them any way they +like. Use `--foo=bar` syntax to make it easier to remove flags (one argument -- one flag). + +Extra bindings could be added at the end of standard bindings because the last binding wins. +However, in order to handle fake actions such as `z4h-accept-and-repeat` it'll probably be necessary +to resolve binding conflicts within z4h. The goal is to allow this syntax for disabling continuous +completion: + +```zsh +zstyle :z4h:expand-or-complete:fzf extra-bindings tab:ignore +``` + +`ignore` could be any other legit action. The point is that by default `tab` is bound to +`z4h-accept-and-repeat` but we rebind it. + +--- + +Support preview customization in fzf. At the very least allow changing the size (down to 0) of +preview in history. + +--- + +Ctrl+/ is a bad binding on some keyboard layouts. See #35. + +--- + +`run-help` has some issues with aliases. See +https://github.com/romkatv/zsh4humans/issues/35#issuecomment-657515701. + +--- + +Currently `find` for recursive completions is called with `-xdev`. This should be customizable. +See https://github.com/romkatv/zsh4humans/issues/35#issuecomment-660477146. + +--- + +Define `command_not_found_handler` for more operating systems (similarly to how it's done for +Homebrew and Debian). + +--- + +`command_not_found_handler` should simultaneously use Homebrew and `/usr/lib/command-not-found` if +both are available. + +--- + +Make `Alt+{Up,Left,Right}` work within `fzf`. See [this comment]( + https://github.com/romkatv/zsh4humans/issues/35#issuecomment-674357739). + +--- + +Colorize files in git completions the same way they are shown in `git status`. + +--- + +Propagate arguments of `zsh -ic "..."` trhough `exec` when switching to a different zsh. + +--- + +Make `run-help z4h source` work. + +--- + +`z4h ssh` should start login shell. + +--- + +When doing `exec $_z4h_exe`, preserve `-l`. + +--- + +Document binding syntax in `z4h help bindkey`. + +--- + +Make it safe to continue using an old shell after z4h has been updated in another shell. + +Assuming that flock works, it can be done as follows. Rename `Z4H` to `Z4H_CACHE_DIR` in +`~/.zshenv`. Within `$Z4H_CACHE_DIR` store: + +- `z4h.zsh` +- `last-update-ts` +- `no-chsh` +- `snapshot-00000000` through `snapshot-ffffffff` + +`z4h.zsh` should point `Z4H` to the latest snapshot. If there are none, it should create a unique +temporary snapshot which would later be transformed into a regular snapshot by `main.zsh`. +Preferably this should be the same code path as in `z4h update`. + +`Z4H_CACHE_DIR` should not propagate through `zsh` the way `Z4H` currently propagates. `Z4H` should +still propagate. It seems like there shouldn't be a requirement that `Z4H_CACHE_DIR` gets set to +the same value in the child shell created by `z4h update` as in the parent. + +`main.zsh` should reader-flock its snapshot. If that fails due to the directory being deleted (see +below), it should `exec zsh`. + +Whenever `main.zsh` creates a new snapshot, it should do this while holding a writer-flock on +`$Z4H_CACHE_DIR`. + +`main.zsh` should scan the existing snapshots while holding a writer-flock on `$Z4H_CACHE_DIR` and +delete all that can be writer-flocked. + +On a system where flock always succeeds (WSL1, see https://github.com/Microsoft/WSL/issues/1927), +this would make matters much worse than currently thanks to `main.zsh` thinking that all snapshots +are unused and deleting them. Special code is required in this case. + +--- + +Make this: + +```zsh +% ls foo/.. +``` + +Complete to this: + +```zsh +% ls foo/../ +``` + +However, `..` should never appear in the listing. In addition, this should work as before: + +```zsh +touch ..x +ls .. +``` + +It should complete to `ls ..x `. + +--- + +The three minor issues with the integrated tmux that I've mentioned in +https://github.com/romkatv/zsh4humans/issues/35#issuecomment-719639084 are here: + +```text +3a8eb6f08fb26ffdad79dae6f00c9a80ba30ecc0f0ee0a142322005f1d28710a */home/romka/notes/z4h-tmuw-issues.md +``` + +--- + +See if it's feasible to fix +https://github.com/romkatv/powerlevel10k#horrific-mess-when-resizing-terminal-window by patching +tmux. + +--- + +`kitty @ launch cat` doesn't work. See +https://github.com/romkatv/zsh4humans/issues/35#issuecomment-720134760. + +--- + +`new_os_window_with_cwd` doesn't work in Kitty. See +https://github.com/romkatv/zsh4humans/issues/35#issuecomment-720134760. + +--- + +Make `z4h ssh` work when the target machine doesn't have internet. + +Implement `z4h fetch` that on a local machine would be a wrapper around `curl`/`wget` and on a +remote machine would send a request via the marker to the local. Local machine would fetch the +file (via `z4h fetch`, naturally) and upload it via `ssh host 'cat >$dst.$$ && mv $dst.$$ $dst`. +The file should have error code, stderr and finally the file. + +There is `curl` in `~/.zshenv`. In order to deal with that, add one more condition to `~/.zshenv`: + +```zsh +if command -v z4h >/dev/null 2>&1; then + z4h fetch "$Z4H_URL"/z4h.zsh >"$Z4H"/z4h.zsh.$$ +elif ... +fi +``` + +`z4h` will be a function that can handle nothing but this command. + +`z4h fetch` should have a whitelist of URLs it can handle (for security). + +--- + +Implement `z4h clipboard-{cut,copy,paste}` that can work over ssh. Add this to `.zshrc`: + +```zsh +# Should clipboard-related z4h commands on the local host use +# the OS clipboard ('system') or a file ('file')? +zstyle :z4h: clipboard system + +# Allow remote hosts access to the clipboard of the client? If set to 'no', +# clipboard-related z4h functions on the remote host will use a file. +zstyle ':z4h:ssh:*' client-clipboard no + +# Copy the current command line to clipboard. +z4h bindkey z4h-copy-bufer-to-clipboard Ctrl+X + +alias x='z4h clipboard-copy' # write stdin to clipboard +alias c='z4h clipboard-copy' # write stdin to clipboard and to stdout +alias v='z4h clipboard-paste' # write clipboard to stdout +``` + +--- + +Add `z4h {slurp,barf}` similar to `z4h clipboard-{cut,copy,paste}`. + +--- + +Make Ctrl+R display the preview right on the command line. Before opening it, set +`BUFFER` to `$'..\n\n\n'` so that it scrolls a bit. + +--- + +Set `TERM=screen-256color` by default. Make it easy to override it per-app and the default as well. + +```zsh +zstyle :z4h:terminfo: term screen-256color +zstyle :z4h:terminfo:ssh term screen-256color +zstyle :z4h:terminfo:sudo term screen-256color +``` + +These styles should be consulted only when using tmux with 256 colors. + +Hm, those styles won't work because we need to define functions for all apps (`ssh`, `sudo`, etc.). +This, then? + +```zsh +zstyle :z4h:tmux term screen-256color +zstyle :z4h:tmux force-screen-256color ssh sudo +zstyle :z4h:tmux force-tmux-256color kak vi +``` + +Or maybe hook `TRAPDEBUG` and use the first syntax? + +Or do it like this: + +```zsh +zstyle :z4h: term-spec {ssh,sudo,docker}:{tmux-256color:screen-256color,alacritty:xterm-256color} +``` + +This isn't good. Figure out how to make it possible to add commands without wiping the default ones. + +--- + +Profile tmux when printing a ton of data to the terminal and see if there is an easy way to speed +it up (likely not). + +--- + +`$TTY` is not writable when doing something like this: + +```zsh +% sudo useradd -ms =zsh test +% sudo -iu test +% [[ -w $TTY ]] || echo 'not writable' +``` + +This breaks a bunch of things. For example, Tab doesn't work. To fix this, `dup` one of +the standard file descriptors into `_z4h_tty_fd` on startup and use it through the code instead of +`$TTY`. + +--- + +Change the way Up/Down work with multi-line commands. Pressing Up +twice should always have the effect of fetching from history twice, whether the command line was +empty or not to begin with. + +--- + +Consider changing Up/Down so that it searches for individual words. There +is `HISTORY_SUBSTRING_SEARCH_FUZZY=1` for it. + +--- + +Add a banner to `~/.zshrc` that requires confirmation. Add the same banner to `install`. When +the user consents during the installation, remove the banner from `~/.zshrc`. + +The banner should say that this is bleeding edge, blah, blah. + +--- + +Do not install zsh-bin if the only thing missing from the stock is terminfo. Also remove the +requirement for `zsh/pcre`. Better yet, add a `zstyle` for required modules. + +The goal here is to avoid installing zsh-bin when using macOS Big Sur or having zsh from brew. + +--- + +Make zsh-bin work like `tmux -u`. That is, assume UTF-8 always. If there is no UTF-8 locale on the +machine (or maybe if the current locale is not UTF-8) require zsh-bin. + +--- + +Make integrated tmux work with `TERM=xterm-256color`. + +--- + +Remove client-server architecture from the integrated tmux. Would be nice to put it in the same +process as zsh but it might make it more difficult to update tmux. For starters it's probably a good +idea to have zsh in one process and everything else in another (`z4hd`). + +Actually, maybe this is a bad idea. Maybe it's better to run `z4hd` the way `tmux` currently runs. +Just add `version` to the socket name. The latter can be done right now, without any architectural +changes. + +--- + +Add a special escape code to the integrated tmux that would allow identifying via a roundtrip to +the TTY. Since other terminals won't reply, the logic should be like this: + +1. Write "are you integrated tmux". +2. Write "where is cursor". +3. Read the cursor positions. If a special response preceeds it, this is integrated tmux. + +--- + +Add `z4h [-r] output` that prints the output of the last command. With `-r` the output is printed +without styling (no colors, etc.). Print a warning if the output has more than `N` bytes (or +terminal lines?). `N` should be configurable. + +Implement this by printing a marker in preexec and another in precmd. + +--- + +Complain if users override `TERM` when using integrated tmux. + +--- + +Figure out better key bindings for macOS. + +--- + +If using iTerm2 with the default color scheme, change it to Tango Dark with dark-grey for black. +Do it in `p10k configure` for now. + +--- + +`z4h ssh` should backup remote files to `~/zsh-backup/ssh`. Each file/directory just once. When +backing up, write a message to the tty saying so. Have a `zstyle` to control this. + +--- + +List `~/.tmux.conf` in `~/.zshrc` among the files to send over ssh. + +--- + +Make it possible for users to tell which commit their zsh4humans is synced to. + +--- + +Implement a benchmark that measures the startup time. + +```zsh +print 'print foo""bar; exit' | script -E never -qec "script -E never -f -T /tmp/timing -qe /tmp/script" /dev/null >/dev/null +``` + +--- + +Add a `zstyle` to disable VTE integration. + +--- + +Run recovery code from `z4h.zsh` on `precmd` to lower startup lag (perhaps by defining +`-z4h-post-init` and later redefining it). This should also solve the problem with custom `HISTFILE` +-- just `fc -R` it on recovery. + +--- + +When using `su blah -` with the source and the target users both having z4h with integrated tmux, +screen gets cleared unnecessarily. See https://github.com/romkatv/zsh4humans/issues/159. This can +be fixed by creating world-writable directory `/tmp/z4h-tty` with world-writabable files in it. +The names of the files would be derived from `$TTY` of tmux (both integrated and real) and the +content would have `$TTY`, `$_Z4H_TMUX`, etc. Note that `$_Z4H_TMUX_CMD` may be unaccessible, so +some fallback would be needed. If `$_Z4H_TMUX_CMD` is integrated tmux, then it should be OK to use +the integrated tmux from the target user provided that it's compatible. So some version would need +to be included in the file. + +--- + +Disable builtin `log` in v6. + +--- + +Add an ability to set `ZDOTDIR` for remote machines: + +```zsh +zstyle :z4h:ssh:some-host zdotdir '~/.config/zsh' +``` + +Consider setting the default value to something other than `$HOME`. A naive solution will break +this: + +```zsh +sudo -su $USER +``` + +No one's gonna do *that* but there might be other ways of losing `ZDOTDIR` even when it's exported. + +--- + +Make the list of traversed directories in `z4h-cd-down` configurable. + +```zsh +zstyle ':z4h:cd-down' dirs history descendants:. descendants:~ children:/ +``` + +Could use better naming. + +Make sure that the `find` command that corresponds to `descendants:~` prunes `.`. Entries from +history can be filtered out at the end. + +--- + +Add `undo-repeat` action (or a better name) to fzf widgets. It should completely undo `repeat`. +Great to bind on Shift+Tab. + +--- + +Delete (uninstall) stuff that was installed with `z4h install` and no longer has the corresponding +directive. + +--- + +When generating a config in p10k under z4h, create trampoline files for more powerful terminals. +E.g., when generating `.p10k-ascii-8color.zsh`, create all the other variants that don't exist yet. + +Adjust the welcome message about tmux. + +--- + +Add every `.p10k*.zsh` variant that doesn't exist locally to `z4h_ssh_receive_files`. The idea is to +pull configs that get generated remotely. + +--- + +If there is no correct `.p10k*.zsh` variant for the TTY but there are variants for less powerful +terminals, automatically create trampoline files. + +--- + +If there is no correct `.p10k*.zsh` variant for the TTY but there are variants for more powerful +terminals, print a message explaning why the configuration wizard is about to start. Maybe also +ask for other options? Maybe use a predefined simple config and print a message? + +--- + +Create `.tmux.conf` in the installer if there is no existing config and the user chooses +"always tmux". + +--- + +Find a better solution w.r.t. functions `docker` and `sudo`. + +--- + +Change the default values of `:z4h:term-title:ssh` in v6 to these: + +```zsh +zstyle ':z4h:term-title:ssh' precmd ${${${Z4H_SSH##*:}//\%/%%}:-%m}': %~' +zstyle ':z4h:term-title:ssh' preexec ${${${Z4H_SSH##*:}//\%/%%}:-%m}': ${1//\%/%%}' +``` + +--- + +Make p10k display `${${${Z4H_SSH##*:}//\%/%%}:-%m}` instead of `%m` in `context`. Not sure how to +achieve this without making the default p10k configs more complex. + +--- + +When replacing instant prompt with the real thing, it's not necessary to print `ed`. Zsh will do it +on its own. Removing this `ed` might reduce the magnitude of blinking. It also might make it +possible to get rid of the function call in `PS1`. + +--- + +Implement rzw (Roman's Zsh Widgets) in a separate repo. + +```zsh +# zwords has an even number of elements, all of which are indices into $PREBUFFER$BUFFER. +# All odd words (one-based) are whitespace. +# $zwords[2*izword-1] is the start position of the word the cursor is pointing to. +# $zwords[2*izword] is the end position. +# $zword is the actual word. It is technically redundant. +rzw-parse-buffer -a zwords -i izword -w zword + +# Returns 1 if there are no non-whitespace words in $PREBUFFER$BUFFER. +# Otherwise returns 0 and sets the specified parameters to the current word, or +# the one before it if the current word is whitespace, or the one after it if +# there is no previous word. +rzw-get-prev-zword -s istart -e iend -w zword + +# Finds the difference between two strings using Levenshtein distance. +# +# % rzw-string-diff -a parts -- 'sunday!' 'saturday' +# % printf '%-5s => %s\n' ${(qq)parts} +# 's' => 's' +# '' => 'at' +# 'u' => 'u' +# 'n' => 'r' +# 'day' => 'day' +# '!' => '' +# +# The default values of -m and -M are set like this: +# +# zstyle :rzw:WIDGET: string-diff-max-complexity 10000 1000000 +# +# If only one value is set, the second defaults to it. If neither is set, the +# values default to 10000 and 1000000. TODO: verify that the second value is +# adequate. +# +# If -c is not specified, it is fetched like this: +# +# zstyle :rzw:WIDGET: string-diff-cache-file +# +# Which in turn defaults to this: +# +# ${XDG_CACHE_HOME:-$HOME/.cache}/rzw-string-diff-$OSTYPE-$CPUTYPE +rzw-string-diff -m 10000 -M 1000000 -c CACHE -a parts -- s1 s2 + +# Compile a C implementation of rzw-string-diff into CACHE (a file). +# Does nothing if CACHE is a readable and executable file. +# +# Refuses to create directories whose parent isn't owned by $USER. +# If -c is not specified, its value is resolved as described above. +rzw-string-diff -C -c CACHE + +# Does nothing if istart lies outside of $BUFFER. Otherwise replaces the +# specified range with the specified content while keeping the cursor +# pointing to the same content as before. +rzw-replace-buffer -- istart iend content + +# Returns 0 if the script is a valid body of a function with the current +# options, aliases, etc. Otherwise returns 1. +rzw-is-valid-script -- script + +# Quotes the shell word to the left of the cursor with double or single quotes. +# Prefers quotes with the shorter result or double quotes if there is no +# difference. If already quoted, changes quotes. If there is an unterminated +# quote, terminates it and quotes the resulting word with the same kind of +# quotes. Keeps the cursor on the content it was pointing to before the widget +# was invoked. + +# Replaces the shell word to the left of the cursor with the result of the +# specified transformation. Keeps the cursor on the content it was pointing to +# before the widget was invoked. +# +# Prior to the transformation the shell word is unquoted. Afterwards it is +# quoted while preferring the same kind of quoting that already existed prior +# to the transformation. +# +# bar => /foo/bar +# 'bar' => '/foo/bar' +# 'bar => '/foo/bar' +# $'b\x20r' => $'/foo/b r' +# $'b\141r' => $'/foo/bar' +# +# Note: Pass the original unquoted word as $2? +rzw-transform-prev-zword -- 'REPLY=${1:a}' + +# Example + +function convert-prev-zword-to-abolute-path() { + rzw-transform-prev-zword -- 'REPLY=${1:a}' +} + +zle -N convert-prev-zword-to-abolute-path +bindkey '^A' convert-prev-zword-to-abolute-path +``` + +The buffer is always `$PREBUFFER$BUFFER` and all buffer indices are within it. diff --git a/fn/ssh-teleportation.asciinema b/fn/ssh-teleportation.asciinema new file mode 100644 index 0000000..06f5275 --- /dev/null +++ b/fn/ssh-teleportation.asciinema @@ -0,0 +1,162 @@ +{"version": 2, "width": 78, "height": 24, "timestamp": 1670178844, "env": {"SHELL": "/usr/local/bin/zsh", "TERM": "tmux-256color"}, "title": "zsh4humans ssh teleportation"} +[0.041554, "o", "\r\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n\u001bM\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[34h\u001b[?25h"] +[1.832626, "o", "\u001b[38;5;96m# Let's add an alias to zsh.\u001b[m\u000f"] +[2.612556, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[38;5;96m# Let's add an alias to zsh.\u001b[39m \u001b[38;5;81mlaptop\u001b[22;31H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[4.283083, "o", "\u001b[32mecho\u001b[39m \u001b[33m'alias say=echo'\u001b[39m \u001b[33m>>\u001b[39m\u001b[4m.zshrc\u001b[m\u000f"] +[4.58029, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[32mecho\u001b[39m \u001b[33m'alias say=echo'\u001b[39m \u001b[33m>>\u001b[39m\u001b[4m.zshrc\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[22;33H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25h \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[5.410632, "o", "\u001b[32m\u001b[4mexec\u001b[m\u000f \u001b[32mzsh\u001b[m\u000f"] +[5.899933, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[32m\u001b[4mexec\u001b[m\u000f \u001b[32mzsh\u001b[39m \u001b[38;5;81mlaptop\r\n\r\n\r\n\u001bM\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[34h\u001b[?25h"] +[7.240476, "o", "\u001b[32msay\u001b[39m hi from laptop"] +[7.766052, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[32msay\u001b[39m hi from laptop \u001b[38;5;81mlaptop\u001b[22;21H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25hhi from laptop\r\n \u001b[23;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\n\u001b[K\u001bM\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[9.609782, "o", "\u001b[38;5;96m# Let's see what we have on a build server.\u001b[m\u000f"] +[9.971503, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[38;5;96m# Let's see what we have on a build server.\u001b[39m \u001b[38;5;81mlaptop\u001b[22;46H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[12.190352, "o", "\u001b[32mssh\u001b[39m build-server-1 \u001b[33m'which zsh'\u001b[m\u000f"] +[12.427713, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[32mssh\u001b[39m build-server-1 \u001b[33m'which zsh'\u001b[39m \u001b[38;5;81mlaptop\u001b[22;33H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25h \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;196m❯\u001b[39m \u001b[K\u001b[?2004h"] +[15.486705, "o", "\u001b[32mssh\u001b[39m build-server-1 \u001b[33m'echo $SHELL'\u001b[m\u000f"] +[15.715784, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;196m❯\u001b[39m \u001b[32mssh\u001b[39m build-server-1 \u001b[33m'echo $SHELL'\u001b[39m \u001b[38;5;81mlaptop\u001b[22;35H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25h/usr/bin/bash\r\n \u001b[23;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\n\u001b[K\u001bM\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[18.832325, "o", "\u001b[32mssh\u001b[39m build-server-1 \u001b[33m'whoami'\u001b[m\u000f"] +[18.979803, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[32mssh\u001b[39m build-server-1 \u001b[33m'whoami'\u001b[39m \u001b[38;5;81mlaptop\u001b[22;30H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25hdev\r\n \u001b[23;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\n\u001b[K\u001bM\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[21.875165, "o", "\u001b[38;5;96m# It's bash and there is no zsh.\u001b[m\u000f"] +[22.043239, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[38;5;96m# It's bash and there is no zsh.\u001b[39m \u001b[38;5;81mlaptop\u001b[22;35H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[24.564545, "o", "\u001b[38;5;96m# Let's log in.\u001b[m\u000f"] +[24.754848, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[38;5;96m# Let's log in.\u001b[39m \u001b[38;5;81mlaptop\u001b[22;18H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[25.595644, "o", "\u001b[32mssh\u001b[39m build-server-1"] +[25.899655, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[32mssh\u001b[39m build-server-1 \u001b[38;5;81mlaptop\u001b[22;21H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25h"] +[26.095838, "o", "\u001b[33mz4h\u001b[39m: fetching \u001b[4mz4h.zsh\r\n\u001b[m\u000f\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh4humans\r\n\u001b[m\u000f"] +[26.586142, "o", "\u001b[33mz4h\u001b[39m: cannot find usable \u001b[32mzsh\u001b[39m\r\n\u001b[K\u001b[33mz4h\u001b[39m: fetching \u001b[1mzsh 5.8\u001b[m\u000f installer\r\n\u001b[K\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh 5.8\u001b[m\u000f to \u001b[4m~/.local\u001b[m\u000f\r\n\u001b[K"] +[28.271259, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1msystemd\u001b[m\u000f completions\r\n\u001b[K"] +[28.813509, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh-history-substring-search\u001b[m\u000f\r\n\u001b[K\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh-autosuggestions\u001b[m\u000f\r\n\u001b[K\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh-completions\u001b[m\u000f\r\n\u001b[K"] +[29.353048, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh-syntax-highlighting\u001b[m\u000f\r\n\u001b[K"] +[29.552046, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mterminfo\u001b[m\u000f\r\n\u001b[K"] +[30.688842, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mfzf\u001b[m\u000f\r\n\u001b[K\u001b[33mz4h\u001b[39m: fetching \u001b[1mfzf\u001b[m\u000f binary\r\n\u001b[K\u001b[33mz4h\u001b[39m: installing \u001b[1mtmux\u001b[m\u000f\r\n\u001b[K\u001b[33mz4h\u001b[39m: installing \u001b[1mpowerlevel10k\u001b[m\u000f\r\n\u001b[K\u001b[33mz4h\u001b[39m: initializing \u001b[32mzsh\u001b[39m\r\n\u001b[K"] +[30.856867, "o", "\u001b[?25l\u001b[34h\u001b[?25h \u001b[24;1H"] +[31.36368, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\u001b[K\n\u001b[K\n\u001b[K\u001bM\u001b[38;5;39m\u001b[48;5;236m\u001b[1m~\u001b[m\u000f\u001b[48;5;236m \u001b[38;5;81mbuild-server-1\u001b[24;1H\u001b[49m\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[32.779078, "o", "\u001b[32mwhich\u001b[39m zsh"] +[32.994224, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[32mwhich\u001b[39m zsh \u001b[38;5;81m\u001b[48;5;236mbuild-server-1\u001b[23;1H\u001b[?2004l\u001b[m\u000f\r\n\n\u001b[K\u001b[2A/home/dev/.local/bin/zsh\r\n \u001b[24;1H\u001b[J\u001bM\u001b[K\r\n\n\u001b[K\u001bM\u001b[38;5;39m\u001b[48;5;236m\u001b[1m~\u001b[m\u000f\u001b[48;5;236m \u001b[38;5;81mbuild-server-1\u001b[24;1H\u001b[49m\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h\u001b[?25l\u001b[34h\u001b[?25h"] +[35.189504, "o", "\u001b[38;5;96m# Missing software was installed to $HOME.\u001b[m\u000f"] +[35.904331, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[?2004l\u001b[38;5;96m# Missing software was installed to $HOME.\u001b[39m \u001b[38;5;81m\u001b[48;5;236mbuild-server-1\u001b[23;1H\u001b[m\u000f\r\n\n\u001b[K\u001b[2A \u001b[22;1H\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[48;5;236m\u001b[1m~\u001b[m\u000f\u001b[48;5;236m \u001b[38;5;81mbuild-server-1\u001b[24;1H\u001b[49m\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h\u001b[?25l\u001b[34h\u001b[?25h"] +[37.989749, "o", "\u001b[32msay\u001b[39m hi from build-server-1"] +[38.472694, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[?2004l\u001b[32msay\u001b[39m hi from build-server-1 \u001b[38;5;81m\u001b[48;5;236mbuild-server-1\u001b[39m\u001b[49m\u001b[24;1H\n\u001b[K\u001b[2Ahi from build-server-1\r\n \u001b[24;1H\u001b[J\u001bM\u001b[K\r\n\n\u001b[K\u001bM\u001b[38;5;39m\u001b[48;5;236m\u001b[1m~\u001b[m\u000f\u001b[48;5;236m \u001b[38;5;81mbuild-server-1\u001b[24;1H\u001b[49m\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h\u001b[?25l\u001b[34h\u001b[?25h"] +[40.362121, "o", "\u001b[38;5;96m# RC files got copied over, so our alias works.\u001b[m\u000f"] +[41.280027, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[38;5;96m# RC files got copied over, so our alias works.\u001b[39m \u001b[38;5;81m\u001b[48;5;236mbuild-server-1\u001b[22;50H\u001b[?2004l\u001b[m\u000f\r\n\r\n\n\u001b[K\u001b[2A \u001b[22;1H\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[48;5;236m\u001b[1m~\u001b[m\u000f\u001b[48;5;236m \u001b[38;5;81mbuild-server-1\u001b[24;1H\u001b[49m\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h\u001b[?25l\u001b[34h\u001b[?25h"] +[44.465618, "o", "\u001b[32mexit\u001b[m\u000f"] +[45.113432, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[?2004l\u001b[32mexit\u001b[39m \u001b[38;5;81m\u001b[48;5;236mbuild-server-1\u001b[39m\u001b[49m\u001b[24;1H\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25h \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[46.563402, "o", "\u001b[38;5;96m# Prompt doesn't have a highlight.\u001b[m\u000f"] +[46.994788, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[38;5;96m# Prompt doesn't have a highlight.\u001b[39m \u001b[38;5;81mlaptop\u001b[22;37H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[48.474431, "o", "\u001b[38;5;96m# It means we are back on the local machine.\u001b[m\u000f"] +[49.290855, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[?2004l\u001b[38;5;96m# It means we are back on the local machine.\u001b[39m \u001b[38;5;81mlaptop\u001b[39m\u001b[24;1H\n\u001b[K\u001b[2A \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[50.594034, "o", "\u001b[38;5;96m# Let's ssh again.\u001b[m\u000f"] +[51.442656, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[38;5;96m# Let's ssh again.\u001b[39m \u001b[38;5;81mlaptop\u001b[22;21H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[52.56407, "o", "\u001b[32mssh\u001b[39m build-server-1"] +[52.818893, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[?2004l\u001b[32mssh\u001b[39m build-server-1 \u001b[38;5;81mlaptop\u001b[22;21H\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25h"] +[53.036515, "o", "\r\n\u001b[38;5;39m\u001b[48;5;236m\u001b[1m~\u001b[m\u000f\u001b[48;5;236m \u001b[38;5;81mbuild-server-1\u001b[24;1H\u001b[49m\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h\u001b[?25l\u001b[34h\u001b[?25h"] +[54.484448, "o", "\u001b[38;5;96m# It's fast because everything is installed.\u001b[m\u000f"] +[55.043642, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[38;5;96m# It's fast because everything is installed.\u001b[39m \u001b[38;5;81m\u001b[48;5;236mbuild-server-1\u001b[22;47H\u001b[?2004l\u001b[m\u000f\r\n\r\n\n\u001b[K\u001b[2A \u001b[22;1H\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[48;5;236m\u001b[1m~\u001b[m\u000f\u001b[48;5;236m \u001b[38;5;81mbuild-server-1\u001b[24;1H\u001b[49m\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h\u001b[?25l\u001b[34h\u001b[?25h"] +[56.482294, "o", "\u001b[32mexit\u001b[m\u000f"] +[56.98184, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[32mexit\u001b[39m \u001b[38;5;81m\u001b[48;5;236mbuild-server-1\u001b[23;1H\u001b[?2004l\u001b[m\u000f\r\n\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25h \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[58.203896, "o", "\u001b[38;5;96m# Let's try another build server.\u001b[m\u000f"] +[58.970732, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[38;5;96m# Let's try another build server.\u001b[39m \u001b[38;5;81mlaptop\u001b[22;36H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[59.954156, "o", "\u001b[32mssh\u001b[39m build-server-2"] +[60.154842, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[32mssh\u001b[39m build-server-2 \u001b[38;5;81mlaptop\u001b[22;21H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25h"] +[60.416622, "o", "\u001b[33mz4h\u001b[39m: fetching \u001b[4mz4h.zsh\u001b[m\u000f\r\n"] +[60.636135, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh4humans\r\n\u001b[m\u000f"] +[60.857417, "o", "\u001b[33mz4h\u001b[39m: cannot find usable \u001b[32mzsh\u001b[39m\r\n\u001b[K\u001b[33mz4h\u001b[39m: fetching \u001b[1mzsh 5.8\u001b[m\u000f installer\r\n\u001b[K\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh 5.8\u001b[m\u000f to \u001b[4m~/.local\u001b[m\u000f\r\n\u001b[K"] +[62.702301, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1msystemd\u001b[m\u000f completions\r\n\u001b[K"] +[63.290105, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh-history-substring-search\u001b[m\u000f\r\n\u001b[K\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh-autosuggestions\u001b[m\u000f\r\n\u001b[K\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh-completions\u001b[m\u000f\r\n\u001b[K"] +[63.939699, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh-syntax-highlighting\u001b[m\u000f\r\n\u001b[K\u001b[33mz4h\u001b[39m: installing \u001b[1mterminfo\u001b[m\u000f\r\n\u001b[K"] +[65.022333, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mfzf\u001b[m\u000f\r\n\u001b[K\u001b[33mz4h\u001b[39m: fetching \u001b[1mfzf\u001b[m\u000f binary\r\n\u001b[K"] +[65.162134, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mtmux\u001b[m\u000f\r\n\u001b[K\u001b[33mz4h\u001b[39m: installing \u001b[1mpowerlevel10k\u001b[m\u000f\r\n\u001b[K\u001b[33mz4h\u001b[39m: initializing \u001b[32mzsh\u001b[m\u000f\r\n\u001b[K"] +[65.342964, "o", "\u001b[?25l\u001b[34h\u001b[?25h \u001b[24;1H"] +[65.839571, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\u001b[K\n\u001b[K\n\u001b[K\u001bM\u001b[38;5;39m\u001b[48;5;236m\u001b[1m~\u001b[m\u000f\u001b[48;5;236m \u001b[38;5;81mbuild-server-2\u001b[24;1H\u001b[49m\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[66.915453, "o", "\u001b[32mhistory\u001b[m\u000f"] +[67.375355, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[?2004l\u001b[32mhistory\u001b[39m \u001b[38;5;81m\u001b[48;5;236mbuild-server-2\u001b[22;10H\u001b[m\u000f\r\n\r\n\n\u001b[K\u001b[2A 1 which zsh\r\n 2 say hi from build-server-1\r\n \u001b[24;1H\u001b[K\n\u001b[K\n\u001b[K\u001bM\u001b[38;5;39m\u001b[48;5;236m\u001b[1m~\u001b[m\u000f\u001b[48;5;236m \u001b[38;5;81mbuild-server-2\u001b[24;1H\u001b[49m\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h\u001b[?25l\u001b[34h\u001b[?25h"] +[70.838394, "o", "\u001b[38;5;96m# History between all build servers is shared.\u001b[m\u000f"] +[71.843116, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[?2004l\u001b[38;5;96m# History between all build servers is shared.\u001b[39m \u001b[38;5;81m\u001b[48;5;236mbuild-server-2\u001b[22;1H\u001b[m\u000f\u001b[48d\n\u001b[K\u001b[2A \u001b[22;1H\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[48;5;236m\u001b[1m~\u001b[m\u000f\u001b[48;5;236m \u001b[38;5;81mbuild-server-2\u001b[24;1H\u001b[49m\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h\u001b[?25l\u001b[34h\u001b[?25h"] +[74.636766, "o", "\u001b[32msay\u001b[39m hi from build-server-2"] +[75.027652, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[?2004l\u001b[32msay\u001b[39m hi from build-server-2 \u001b[38;5;81m\u001b[48;5;236mbuild-server-2\u001b[22;29H\u001b[m\u000f\r\u001b[48d\n\u001b[K\u001b[2Ahi from build-server-2\r\n \u001b[24;1H\u001b[J\u001bM\u001b[K\r\n\n\u001b[K\u001bM\u001b[38;5;39m\u001b[48;5;236m\u001b[1m~\u001b[m\u000f\u001b[48;5;236m \u001b[38;5;81mbuild-server-2\u001b[24;1H\u001b[49m\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h\u001b[?25l\u001b[34h\u001b[?25h"] +[77.632216, "o", "\u001b[32mexit\u001b[m\u000f"] +[77.950285, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[?2004l\u001b[32mexit\u001b[39m \u001b[38;5;81m\u001b[48;5;236mbuild-server-2\u001b[39m\u001b[49m\u001b[24;1H\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25h \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[79.113531, "o", "\u001b[38;5;96m# Suppose our build server got wiped and rebuilt.\u001b[m\u000f"] +[79.634653, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[38;5;96m# Suppose our build server got wiped and rebuilt.\u001b[39m \u001b[38;5;81mlaptop\u001b[22;52H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[81.299492, "o", "\u001b[32mssh\u001b[39m build-server-2 \u001b[33m'rm -rf ~/* ~/.*'\u001b[m\u000f"] +[82.163298, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[32mssh\u001b[39m build-server-2 \u001b[33m'rm -rf ~/* ~/.*'\u001b[39m \u001b[38;5;81mlaptop\u001b[22;39H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25h"] +[82.406818, "o", " \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[85.188631, "o", "\u001b[32mssh\u001b[39m build-server-2"] +[85.860285, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[32mssh\u001b[39m build-server-2 \u001b[38;5;81mlaptop\u001b[22;21H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25h"] +[86.122379, "o", "\u001b[33mz4h\u001b[39m: fetching \u001b[4mz4h.zsh\r\n\u001b[m\u000f"] +[86.24062, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh4humans\r\n\u001b[m\u000f"] +[86.44005, "o", "\u001b[33mz4h\u001b[39m: cannot find usable \u001b[32mzsh\u001b[39m\r\n\u001b[K\u001b[33mz4h\u001b[39m: fetching \u001b[1mzsh 5.8\u001b[m\u000f installer\r\n\u001b[K\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh 5.8\u001b[m\u000f to \u001b[4m~/.local\u001b[m\u000f\r\n\u001b[K"] +[88.318785, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1msystemd\u001b[m\u000f completions\r\n\u001b[K"] +[88.709273, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh-history-substring-search\u001b[m\u000f\r\n\u001b[K"] +[88.907472, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh-autosuggestions\u001b[m\u000f\r\n\u001b[K"] +[89.068137, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh-completions\u001b[m\u000f\r\n\u001b[K"] +[89.321291, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh-syntax-highlighting\u001b[m\u000f\r\n\u001b[K"] +[89.519017, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mterminfo\u001b[m\u000f\r\n\u001b[K"] +[90.719638, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mfzf\u001b[m\u000f\r\n\u001b[K\u001b[33mz4h\u001b[39m: fetching \u001b[1mfzf\u001b[m\u000f binary\r\n\u001b[K\u001b[33mz4h\u001b[39m: installing \u001b[1mtmux\u001b[m\u000f\r\n\u001b[K\u001b[33mz4h\u001b[39m: installing \u001b[1mpowerlevel10k\u001b[m\u000f\r\n\u001b[K\u001b[33mz4h\u001b[39m: initializing \u001b[32mzsh\u001b[39m\r\n\u001b[K"] +[90.877017, "o", "\u001b[?25l\u001b[34h\u001b[?25h \u001b[24;1H"] +[91.383459, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\u001b[K\n\u001b[K\n\u001b[K\u001bM\u001b[38;5;39m\u001b[48;5;236m\u001b[1m~\u001b[m\u000f\u001b[48;5;236m \u001b[38;5;81mbuild-server-2\u001b[24;1H\u001b[49m\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[92.72947, "o", "\u001b[38;5;96m# Everything got installed once again.\u001b[m\u000f"] +[94.156124, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[38;5;96m# Everything got installed once again.\u001b[39m \u001b[38;5;81m\u001b[48;5;236mbuild-server-2\u001b[24;1H\u001b[?2004l\u001b[m\u000f\n\u001b[K\u001b[2A \u001b[22;1H\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[48;5;236m\u001b[1m~\u001b[m\u000f\u001b[48;5;236m \u001b[38;5;81mbuild-server-2\u001b[24;1H\u001b[49m\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h\u001b[?25l\u001b[34h\u001b[?25h"] +[95.835927, "o", "\u001b[32mhistory\u001b[m\u000f"] +[96.634429, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[32mhistory\u001b[39m \u001b[38;5;81m\u001b[48;5;236mbuild-server-2\u001b[22;1H\u001b[?2004l\u001b[m\u000f\r\n\r\n\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25h 1 which zsh\r\n 2 say hi from build-server-1\r\n 3 say hi from build-server-2\r\n\u001b[K \u001b[24;1H\u001b[K\n\u001b[K\n\u001b[K\u001bM\u001b[38;5;39m\u001b[48;5;236m\u001b[1m~\u001b[m\u000f\u001b[48;5;236m \u001b[38;5;81mbuild-server-2\u001b[24;1H\u001b[49m\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h\u001b[?25l\u001b[34h\u001b[?25h"] +[100.274941, "o", "\u001b[38;5;96m# Our history is still there.\u001b[m\u000f"] +[100.581006, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[38;5;96m# Our history is still there.\u001b[39m \u001b[38;5;81m\u001b[48;5;236mbuild-server-2\u001b[22;32H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A \u001b[22;1H\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[48;5;236m\u001b[1m~\u001b[m\u000f\u001b[48;5;236m \u001b[38;5;81mbuild-server-2\u001b[24;1H\u001b[49m\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h\u001b[?25l\u001b[34h\u001b[?25h"] +[101.997251, "o", "\u001b[32mexit\u001b[m\u000f"] +[102.194443, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[32mexit\u001b[39m \u001b[38;5;81m\u001b[48;5;236mbuild-server-2\u001b[23;1H\u001b[?2004l\u001b[m\u000f\r\n\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25h \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[103.438198, "o", "\u001b[38;5;96m# Where did history come from?\u001b[m\u000f"] +[103.785972, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[38;5;96m# Where did history come from?\u001b[39m \u001b[38;5;81mlaptop\u001b[22;33H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[105.079585, "o", "\u001b[38;5;96m# Didn't we just wipe the machine?\u001b[m\u000f"] +[105.298053, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[?2004l\u001b[38;5;96m# Didn't we just wipe the machine?\u001b[39m \u001b[38;5;81mlaptop\u001b[22;37H\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[106.7535, "o", "\u001b[32mls\u001b[39m -1 .zsh_history\u001b[34m*\u001b[m\u000f"] +[106.939775, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[32mls\u001b[39m -1 .zsh_history\u001b[34m*\u001b[39m \u001b[38;5;81mlaptop\u001b[22;22H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25h.zsh_history.laptop\r\n.zsh_history.laptop:build-server-1\r\n.zsh_history.laptop:build-server-2\r\n\u001b[K \u001b[24;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\u001b[K\n\u001b[K\n\u001b[K\u001bM\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[110.158693, "o", "\u001b[38;5;96m# All history is preserved on the local machine.\u001b[m\u000f"] +[110.482686, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[38;5;96m# All history is preserved on the local machine.\u001b[39m \u001b[38;5;81mlaptop\u001b[22;51H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[113.033461, "o", "\u001b[32mcat\u001b[39m \u001b[4m.zsh_history.laptop:build-server-2\u001b[m\u000f"] +[113.842297, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[32mcat\u001b[39m \u001b[4m.zsh_history.laptop:build-server-2\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[22;41H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25h: 1670178920:0;say hi from build-server-2\r\n \u001b[23;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\n\u001b[K\u001bM\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[117.001732, "o", "\u001b[38;5;96m# Let's check out the web server.\u001b[m\u000f"] +[117.393545, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[38;5;96m# Let's check out the web server.\u001b[39m \u001b[38;5;81mlaptop\u001b[22;36H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[119.410685, "o", "\u001b[32mssh\u001b[39m web-server"] +[120.401939, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[32mssh\u001b[39m web-server \u001b[38;5;81mlaptop\u001b[22;17H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25h"] +[120.599092, "o", "\u001b[33mz4h\u001b[39m: fetching \u001b[4mz4h.zsh\r\n\u001b[m\u000f"] +[120.729054, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh4humans\r\n\u001b[m\u000f"] +[121.167444, "o", "\u001b[33mz4h\u001b[39m: cannot find usable \u001b[32mzsh\u001b[39m\r\n\u001b[K\u001b[33mz4h\u001b[39m: fetching \u001b[1mzsh 5.8\u001b[m\u000f installer\r\n\u001b[K\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh 5.8\u001b[m\u000f to \u001b[4m~/.local\u001b[m\u000f\r\n\u001b[K"] +[123.423747, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1msystemd\u001b[m\u000f completions\r\n\u001b[K"] +[124.142345, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh-history-substring-search\u001b[m\u000f\r\n\u001b[K\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh-autosuggestions\u001b[m\u000f\r\n\u001b[K\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh-completions\u001b[m\u000f\r\n\u001b[K"] +[124.881095, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mzsh-syntax-highlighting\u001b[m\u000f\r\n\u001b[K"] +[125.11201, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mterminfo\u001b[m\u000f\r\n\u001b[K"] +[126.528135, "o", "\u001b[33mz4h\u001b[39m: installing \u001b[1mfzf\u001b[m\u000f\r\n\u001b[K\u001b[33mz4h\u001b[39m: fetching \u001b[1mfzf\u001b[m\u000f binary\r\n\u001b[K\u001b[33mz4h\u001b[39m: installing \u001b[1mtmux\u001b[m\u000f\r\n\u001b[K\u001b[33mz4h\u001b[39m: installing \u001b[1mpowerlevel10k\u001b[m\u000f\r\n\u001b[K\u001b[33mz4h\u001b[39m: initializing \u001b[32mzsh\u001b[39m\r\n\u001b[K"] +[126.72831, "o", "\u001b[?25l\u001b[34h\u001b[?25h \u001b[24;1H"] +[127.413336, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\u001b[K\n\u001b[K\n\u001b[K\u001bM\u001b[38;5;39m\u001b[48;5;236m\u001b[1m~\u001b[m\u000f\u001b[48;5;236m \u001b[38;5;81mweb-server\u001b[24;1H\u001b[49m\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[128.486912, "o", "\u001b[32mhistory\u001b[m\u000f"] +[129.016388, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[32mhistory\u001b[39m \u001b[38;5;81m\u001b[48;5;236mweb-server\u001b[22;10H\u001b[?2004l\u001b[m\u000f\r\n\r\n\n\u001b[K\u001b[2A 1 history\r\n \u001b[24;1H\u001b[J\u001bM\u001b[K\r\n\n\u001b[K\u001bM\u001b[38;5;39m\u001b[48;5;236m\u001b[1m~\u001b[m\u000f\u001b[48;5;236m \u001b[38;5;81mweb-server\u001b[24;1H\u001b[49m\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h\u001b[?25l\u001b[34h\u001b[?25h"] +[130.467228, "o", "\u001b[38;5;96m# History is not shared between build and web servers.\u001b[m\u000f"] +[132.239577, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[?2004l\u001b[38;5;96m# History is not shared between build and web servers.\u001b[39m \u001b[38;5;81m\u001b[48;5;236mweb-server\u001b[22;57H\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[48;5;236m\u001b[1m~\u001b[m\u000f\u001b[48;5;236m \u001b[38;5;81mweb-server\u001b[24;1H\u001b[49m\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h\u001b[?25l\u001b[34h\u001b[?25h"] +[133.514983, "o", "\u001b[38;5;96m# That's just how I configured it.\u001b[m\u000f"] +[134.119933, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[38;5;96m# That's just how I configured it.\u001b[39m \u001b[38;5;81m\u001b[48;5;236mweb-server\u001b[22;37H\u001b[?2004l\u001b[m\u000f\r\n\r\n\n\u001b[K\u001b[2A \u001b[22;1H\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[48;5;236m\u001b[1m~\u001b[m\u000f\u001b[48;5;236m \u001b[38;5;81mweb-server\u001b[24;1H\u001b[49m\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h\u001b[?25l\u001b[34h\u001b[?25h"] +[135.37334, "o", "\u001b[38;5;96m# You can define which history each machine can see.\u001b[m\u000f"] +[135.931476, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[38;5;96m# You can define which history each machine can see.\u001b[39m \u001b[38;5;81m\u001b[48;5;236mweb-server\u001b[22;1H\u001b[?2004l\u001b[m\u000f\u001b[48d\n\u001b[K\u001b[2A \u001b[22;1H\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[48;5;236m\u001b[1m~\u001b[m\u000f\u001b[48;5;236m \u001b[38;5;81mweb-server\u001b[24;1H\u001b[49m\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h\u001b[?25l\u001b[34h\u001b[?25h"] +[138.672566, "o", "\u001b[32mexit\u001b[m\u000f"] +[139.31213, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[32mexit\u001b[39m \u001b[38;5;81m\u001b[48;5;236mweb-server\u001b[22;7H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25h \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[140.32852, "o", "\u001b[38;5;96m# Why does ssh command teleport shell environment?\u001b[m\u000f"] +[141.273428, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[38;5;96m# Why does ssh command teleport shell environment?\u001b[39m \u001b[38;5;81mlaptop\u001b[22;53H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[142.689556, "o", "\u001b[32mwhich\u001b[39m \u001b[4mssh\u001b[m\u000f"] +[143.297512, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[32mwhich\u001b[39m \u001b[4mssh\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[22;12H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25hssh () {\nz4h ssh \"$@\"\r\n}\r\n\u001b[K \u001b[24;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\u001b[K\n\u001b[K\n\u001b[K\u001bM\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[144.76538, "o", "\u001b[38;5;96m# Oh, it's a function.\u001b[m\u000f"] +[145.144954, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[38;5;96m# Oh, it's a function.\u001b[39m \u001b[38;5;81mlaptop\u001b[22;25H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[146.889556, "o", "\u001b[38;5;96m# Let's bypass it.\u001b[m\u000f"] +[147.632991, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[38;5;96m# Let's bypass it.\u001b[39m \u001b[38;5;81mlaptop\u001b[22;21H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A \u001b[22;1H\u001b[?25l\u001b[34h\u001b[?25h\u001b[?25l\u001b[34h\u001b[?25h\r\n\u001b[J\u001bM\u001b[K\r\n\u001b[38;5;39m\u001b[1m~\u001b[m\u000f \u001b[38;5;81mlaptop\u001b[24;1H\u001b[38;5;76m❯\u001b[39m \u001b[K\u001b[?2004h"] +[148.599347, "o", "\u001b[32m\u001b[4mcommand\u001b[m\u000f \u001b[32mssh\u001b[39m build-server-1"] +[149.705952, "o", "\u001b[?25l\u001b[34h\u001b[?25h\u001b[23;1H\u001b[J\u001bM\u001b[K\u001b[38;5;76m❯\u001b[39m \u001b[32m\u001b[4mcommand\u001b[m\u000f \u001b[32mssh\u001b[39m build-server-1 \u001b[38;5;81mlaptop\u001b[22;29H\u001b[?2004l\u001b[m\u000f\u001b[24;1H\n\u001b[K\u001b[2A\u001b[?25l\u001b[34h\u001b[?25hLast login: Sun Dec 4 18:34:58 2022\r\ndev@build-server-1:~$ "] +[151.373168, "o", "# The login shell is still bash."] +[152.556719, "o", "\r\ndev@build-server-1:~$ "] +[154.124462, "o", "# Of course it is."] +[154.734398, "o", "\r\n\u001b[Kdev@build-server-1:~$ "] +[155.625594, "o", "# Changing login shell requires password."] +[156.028517, "o", "\r\n\u001b[Kdev@build-server-1:~$ "] +[157.183918, "o", "# We were never asked to enter password."] +[157.510099, "o", "\r\n\u001b[Kdev@build-server-1:~$ "] +[158.907208, "o", "# Thanks for watching!"] +[159.708651, "o", "\r\n\u001b[Kdev@build-server-1:~$ "] +[160.715116, "o", "# https://github.com/romkatv/zsh4humans"] +[161.364545, "o", "\r\n\u001b[Kdev@build-server-1:~$ "] +[165.377687, "o", ""] diff --git a/fn/z4h-accept-line b/fn/z4h-accept-line new file mode 100644 index 0000000..841aa19 --- /dev/null +++ b/fn/z4h-accept-line @@ -0,0 +1,10 @@ +#!/usr/bin/env zsh +# +# z4h-accept-line is just like accept-line except that it inserts \n when +# accept-line would result in a parse error or PS2. + +if -z4h-is-valid-list "$PREBUFFER$BUFFER"; then + zle accept-line +else + LBUFFER+=$'\n' +fi diff --git a/fn/z4h-autosuggest-accept b/fn/z4h-autosuggest-accept new file mode 100644 index 0000000..dfdfed5 --- /dev/null +++ b/fn/z4h-autosuggest-accept @@ -0,0 +1,9 @@ +#!/usr/bin/env zsh + +local -i cursor=CURSOR +{ + typeset -gi CURSOR='_z4h_cursor_max()' + zle autosuggest-accept +} always { + CURSOR=cursor +} diff --git a/fn/z4h-backward-word b/fn/z4h-backward-word new file mode 100644 index 0000000..00a0522 --- /dev/null +++ b/fn/z4h-backward-word @@ -0,0 +1,19 @@ +#!/usr/bin/env zsh + +emulate -L zsh -o extended_glob +local buf w=${WORDCHARS//[[:space:][:alnum:]]} +repeat ${NUMERIC:-1}; do + buf=${LBUFFER%%[[:space:]]#} + if (( $#buf < 2 )); then + buf= + elif [[ $buf == *[[:space:]]? ]]; then + buf[-1]= + elif [[ $buf[-2,-1] != *[[:alnum:]$w]* ]]; then + buf=${buf%%[^[:space:][:alnum:]$w]#} + else + [[ $buf == *[[:alnum:]$w] ]] || buf[-1]= + buf=${buf%%[[:alnum:]$w]#} + fi + (( CURSOR -= $#LBUFFER - $#buf )) +done +return 0 diff --git a/fn/z4h-backward-zword b/fn/z4h-backward-zword new file mode 100644 index 0000000..5acc822 --- /dev/null +++ b/fn/z4h-backward-zword @@ -0,0 +1,14 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" +local word buf tail +repeat ${NUMERIC:-1}; do + buf=$PREBUFFER$BUFFER + for word in '' ${(Z:n:)buf}; do + tail=${${buf:$#word}##[[:space:]]#} + (( $#tail <= $#RBUFFER )) && break + buf=$tail + done + CURSOR=$(($#buf <= $#BUFFER ? $#BUFFER - $#buf : 0)) +done +return 0 diff --git a/fn/z4h-cd-down b/fn/z4h-cd-down new file mode 100644 index 0000000..fbada89 --- /dev/null +++ b/fn/z4h-cd-down @@ -0,0 +1,149 @@ +#!/usr/bin/env zsh + +[[ -v _z4h_tty_fd ]] || return + +autoload +X -Uz -- -z4h-present-files -z4h-cursor-show -z4h-find -z4h-fzf + +local -i dot_glob list_types +[[ -o dot_glob ]] && dot_glob=1 +[[ -o list_types ]] && list_types=1 + +-z4h-set-list-colors :complete:cd:: $list_types +local -i list_colors=$((!$?)) + +{ + -z4h-cursor-hide + + local -i first=1 + while true; do + local -i redraw=0 again=0 + () { + eval "$_z4h_opt" + if (( dot_glob )); then + local dirs=(*(-/DN)) + else + local dirs=(*(-/N)) + fi + local non_empty=(${^${dirs}}/*(D-/Y1N:h:t)) + + if (( first )); then + first=0 + elif (( ! $#dirs )); then + return 0 + fi + + { + -z4h-show-dots '' + + local -i pct=60 + (( _z4h_can_save_restore_screen )) && pct=100 + + local -i height=$(( ! $#non_empty && 100 * ($#dirs + 4) < pct * LINES ? $#dirs + 4 : pct * LINES / 100 )) + + (( height >= 6 )) || (( height = 6 )) + (( height <= LINES - 1 )) || (( height = LINES - 1 )) + + local opts=( + --color=hl:201,hl+:201 + --with-nth=2 + --delimiter='\000' + --ansi + --exact + --no-mouse + --tiebreak=length,begin,index + --no-multi + --border=horizontal + ) + + () { + emulate -L zsh + # Set dot_glob in case the value of find-flags depends on it (via `zstyle -e`). + # Ideally we should run this with user options. + (( dot_glob )) && setopt dot_glob + local -a bin + zstyle -a :z4h:${WIDGET#z4h-} find-command bin + if (( ! $#bin )) && (( $+commands[bfs] )); then + opts+=(--no-sort) + fi + } + + local cursor_y cursor_x + -z4h-get-cursor-pos || return + + if (( _z4h_can_save_restore_screen )); then + opts+=(--no-clear) + if { (( height <= cursor_y - 1 )) && zstyle -T :z4h: prompt-at-bottom } || + (( cursor_y - 1 > LINES - cursor_y && cursor_y - 1 > 4 )) && + { (( height > LINES - cursor_y )) || zstyle -T :z4h: prompt-at-bottom }; then + (( height <= cursor_y - 1 )) || (( height = cursor_y - 1 )) + local move=$'\e[0m\e['$((cursor_y-height))';1H' + opts+=(--layout=default) + elif (( LINES - cursor_y > 4 )); then + (( height <= LINES - cursor_y )) || (( height = LINES - cursor_y )) + local move=$'\e[0m\n\r' + opts+=(--layout=reverse) + else + local -i extra=$((height - LINES + cursor_y)) + print -rnu $_z4h_tty_fd -- ${(pl:$height::\n:)} || return + (( cursor_y += LINES - cursor_y - height )) + local move=$'\e[0m\e['$((cursor_y+1))';1H' + opts+=(--layout=reverse) + fi + local _z4h_saved_screen + -z4h-save-screen || return + else + print -u $_z4h_tty_fd || return + local move= + opts+=(--layout=reverse) + fi + + opts+=(--height=$height) + + { + local choice + choice=$( + unsetopt pipe_fail + exec 2>/dev/null + { + print -r -- $sysparams[pid] + print -rC1 -- $dirs + -z4h-find $dot_glob 1 $non_empty | command sed -n '/\/.*\// s/^..//p' + } | { + local -a pids + IFS=' ' builtin read -rA pids || exit + print -r -- $pids $sysparams[pid] || exit + -z4h-present-files $list_colors $list_types 0 + } | { + local -a pids + IFS=' ' builtin read -rA pids || pids=() + print -rnu $_z4h_tty_fd -- $move + -z4h-cursor-show + 2>&$_z4h_tty_fd -z4h-fzf $opts + (( $#pids )) && builtin kill -- $pids + } always { + -z4h-cursor-hide + }) + [[ -n $choice ]] || return + choice=("${(@f)choice}") + [[ -n $choice[1] ]] && again=1 + cd -- ${choice[2]%%$'\0'*} 2>/dev/null || return + redraw=1 + } always { + if (( _z4h_can_save_restore_screen )); then + -z4h-restore-screen + print -rn -- $'\e[0m\e['$cursor_y';'$cursor_x'H' + else + builtin echoti cuu 1 + (( cursor_x > 1 )) && builtin echoti cuf $((cursor_x-1)) + fi + } + } always { + zle -R + } + } || return + (( redraw )) && -z4h-redraw-prompt 1 + (( again )) || break + done +} always { + -z4h-cursor-show +} diff --git a/fn/z4h-clear-screen-hard-bottom b/fn/z4h-clear-screen-hard-bottom new file mode 100644 index 0000000..a981934 --- /dev/null +++ b/fn/z4h-clear-screen-hard-bottom @@ -0,0 +1,10 @@ +#!/usr/bin/env zsh + +[[ -v _z4h_tty_fd ]] || return + +builtin echoti civis >&$_z4h_tty_fd +builtin print -rnu $_z4h_tty_fd -- $'\e[H\e[2J'"${(pl:$((LINES-1))::\n:)}" +builtin zle -I +builtin zle -R +builtin print -rnu $_z4h_tty_fd -- $'\e[3J' +-z4h-cursor-show diff --git a/fn/z4h-clear-screen-hard-top b/fn/z4h-clear-screen-hard-top new file mode 100644 index 0000000..97d47bb --- /dev/null +++ b/fn/z4h-clear-screen-hard-top @@ -0,0 +1,10 @@ +#!/usr/bin/env zsh + +[[ -v _z4h_tty_fd ]] || return + +builtin echoti civis >&$_z4h_tty_fd +builtin print -rnu $_z4h_tty_fd -- $'\e[H\e[2J' +builtin zle .reset-prompt +builtin zle -R +builtin print -rnu $_z4h_tty_fd -- $'\e[3J' +-z4h-cursor-show diff --git a/fn/z4h-clear-screen-soft-bottom b/fn/z4h-clear-screen-soft-bottom new file mode 100644 index 0000000..3a588c9 --- /dev/null +++ b/fn/z4h-clear-screen-soft-bottom @@ -0,0 +1,11 @@ +#!/usr/bin/env zsh + +[[ -v _z4h_tty_fd ]] || return + +builtin echoti civis >&$_z4h_tty_fd +local -i cursor_x cursor_y +-z4h-get-cursor-pos || cursor_y=0 +builtin print -rnu $_z4h_tty_fd -- "${(pl:$((2 * LINES - cursor_y - 1))::\n:)}" +builtin zle -I +builtin zle -R +-z4h-cursor-show diff --git a/fn/z4h-clear-screen-soft-top b/fn/z4h-clear-screen-soft-top new file mode 100644 index 0000000..cd123db --- /dev/null +++ b/fn/z4h-clear-screen-soft-top @@ -0,0 +1,11 @@ +#!/usr/bin/env zsh + +[[ -v _z4h_tty_fd ]] || return + +builtin echoti civis >&$_z4h_tty_fd +local -i cursor_x cursor_y +-z4h-get-cursor-pos || cursor_y=0 +builtin print -rnu $_z4h_tty_fd -- "${(pl:$((2 * LINES - cursor_y))::\n:)}"$'\e[H\e[2J' +builtin zle .reset-prompt +builtin zle -R +-z4h-cursor-show diff --git a/fn/z4h-eof b/fn/z4h-eof new file mode 100644 index 0000000..c83a94d --- /dev/null +++ b/fn/z4h-eof @@ -0,0 +1,10 @@ +#!/usr/bin/env zsh + +local code=$? +builtin zle || builtin exit code +if [[ "$CONTEXT" == start && -z "$BUFFER" ]]; then + typeset -g precmd_functions=(z4h-eof) + builtin zle -w accept-line +else + zle -w delete-char-or-list +fi diff --git a/fn/z4h-exit b/fn/z4h-exit new file mode 100644 index 0000000..066297f --- /dev/null +++ b/fn/z4h-exit @@ -0,0 +1,11 @@ +#!/usr/bin/env zsh + +local code=$? +builtin eval "$_z4h_opt" +builtin zle || builtin exit code +typeset -g precmd_functions=(z4h-exit) +if [[ $BUFFER == [[:space:]]# ]]; then + builtin zle -w accept-line +else + builtin zle -w send-break +fi diff --git a/fn/z4h-forward-word b/fn/z4h-forward-word new file mode 100644 index 0000000..1ff70c5 --- /dev/null +++ b/fn/z4h-forward-word @@ -0,0 +1,19 @@ +#!/usr/bin/env zsh + +emulate -L zsh -o extended_glob +local buf w=${WORDCHARS//[[:space:][:alnum:]]} +repeat ${NUMERIC:-1}; do + buf=${RBUFFER##[[:space:]]#} + if (( $#buf < 2 )); then + buf= + elif [[ $buf == ?[[:space:]]* ]]; then + buf[1]= + elif [[ $buf[1,2] != *[[:alnum:]$w]* ]]; then + buf=${buf##[^[:space:][:alnum:]$w]#} + else + [[ $buf == [[:alnum:]$w]* ]] || buf[1]= + buf=${buf##[[:alnum:]$w]#} + fi + (( CURSOR += $#RBUFFER - $#buf )) +done +return 0 diff --git a/fn/z4h-forward-zword b/fn/z4h-forward-zword new file mode 100644 index 0000000..ef670f9 --- /dev/null +++ b/fn/z4h-forward-zword @@ -0,0 +1,16 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" +local word buf +repeat ${NUMERIC:-1}; do + buf=$PREBUFFER$BUFFER + for word in ${(Z:n:)buf} ''; do + (( $#buf < $#RBUFFER )) && break + buf=${${buf##[[:space:]]#}:$#word} + done + CURSOR=$(($#BUFFER - $#buf)) + if (( CURSOR > _z4h_cursor_max() )); then + (( --CURSOR )) + fi +done +return 0 diff --git a/fn/z4h-fzf-complete b/fn/z4h-fzf-complete new file mode 100644 index 0000000..f1336af --- /dev/null +++ b/fn/z4h-fzf-complete @@ -0,0 +1,281 @@ +#!/usr/bin/env zsh + +[[ -v _z4h_tty_fd ]] || return + +[[ ! -v _z4h_compinit_fd ]] || -z4h-compinit || true + +[[ -o auto_menu ]] && local -i auto_menu=1 || local -i auto_menu=0 + +setopt local_options always_to_end no_auto_list no_auto_menu no_auto_param_keys \ + no_auto_remove_slash no_bash_auto_list no_complete_in_word no_list_ambiguous \ + no_list_beep no_list_packed no_list_rows_first no_menu_complete no_rec_exact + +local -ra _z4h_shadow_funcs=(_main_complete _multi_parts _path_files _setup compadd zstyle) + +local f +for f in $_z4h_shadow_funcs; do + if (( ${+functions[$f]} )); then + builtin functions -c -- $f -z4h-orig-$f + local _z4h_orig_${f#_}=-z4h-orig-$f + else + eval "local -a _z4h_orig_${f#_}=(builtin $f)" + fi +done + +# exec 3>&2 2>/tmp/log + +{ + # setopt xtrace + + function zstyle() { + # zstyle -T ":completion:${curcontext}:$_type" list-grouped + if [[ $# == 3 && $3 == list-grouped && $1 == -(t|T) && $2 == :completion:* ]]; then + return 1 + else + $_z4h_orig_zstyle "$@" + fi + } + + function _setup () {} + + function _multi_parts() { + local _z4h_sep=${@[-2]} + local _z4h_array=${@[-1]} + shift -p 2 + [[ $# != 0 && ${@[-1]} == (-|--) ]] && shift -p + () { + emulate -L zsh + if [[ $_z4h_array == '('* ]]; then + _z4h_array=(${=_z4h_array[2,-2]}) + else + _z4h_array=("${(@P)_z4h_array}") + fi + } + # This ** is not ideal but alternatives are worse. + # + # Consider the following setup: + # + # git init + # mkdir -p foo1 foo2/bar + # touch foo1/qux foo2/bar/qux + # git add . + # git commit --allow-empty-message -m '' + # rm foo1/qux foo2/bar/qux + # git commit + # + # At this point there are two trial completions: foo1/qux and foo2/bar/qux. + # + # With ** we'll get foo/qux with the cursor after foo. Another TAB will + # give summon fzf to choose between the two trial completions. + # + # With a single * we'll get the same completion on the first TAB (foo/qux + # with the cursor after foo). Another TAB will produce foo1/qux. That's + # quite awful. The awfulness is expecially obvious if auto_menu is set. + # In this case `git commit ` will complete foo1/qux in a single go, + # even though there are two files to commit. + # + # The ideal behavior would be foo on the first TAB and fzf with both + # trial completions on the second. We can achieve this by dropping -M. + # However, this would not allow us to complete f/b, and we want that. + compadd -M "r:|${_z4h_sep}=** r:|=*" "$@" - "${_z4h_array[@]}" + } + + function _path_files() { + () { + # TODO: respect pats and ignore somehow. + local -a opts files dirs pats ignore + zparseopts -a mopts f=files /=dirs g+:=pats F:=ignore \ + P: S: q r: R: W: M+: J+: V+: x+: X+: 1 2 o+: n + local -i patdirs=${#pats} + while (( ${#pats} )); do + if [[ ${pats[2]} != ('*(-/)'|'*(#q-/)') ]]; then + patdirs=0 + break + fi + builtin shift 2 pats + done + (( patdirs )) && dirs=(-/) + (( ${#files} || ! ${#dirs} )) && typeset -gi _z4h_only_dirs=0 + } "$@" + local -ir _z4h_in_path_files=1 + $_z4h_orig_path_files "$@" + } + + function compadd() { + local -a _z4h_{opts,nDOA,q,f,s,S,I,W,i,P,p,d,r,R,e,U} + zparseopts -E -a _z4h_opts {D,O,A}+:=_z4h_nDOA n+=_z4h_nDOA \ + f+=_z4h_f s+:=_z4h_s S+:=_z4h_S I+:=_z4h_I W+:=_z4h_W \ + i+:=_z4h_i P+:=_z4h_P p+:=_z4h_p d+:=_z4h_d q+=_z4h_q \ + r+:=_z4h_r R+:=_z4h_R e+=_z4h_e U+=_z4h_U \ + {a,k,l,1,2,C,Q}+ {F,J,X,x,V,E,M}+: o+:: + if (( ${#_z4h_nDOA} )); then + $_z4h_orig_compadd "$@" + return + fi + local -a _z4h_A _z4h_D + if (( ${#_z4h_d} )); then + () { + emulate -L zsh + local _z4h_arr=$_z4h_d[2] + if [[ $_z4h_arr == '('* ]]; then + _z4h_D=(${=_z4h_arr[2,-2]}) + else + _z4h_D=("${(@P)_z4h_arr}") + fi + } + fi + $_z4h_orig_compadd -A _z4h_A -D _z4h_D "$@" || true + (( ${#_z4h_A} )) || return + # Many completers read $compstate[nmatches], which can be super slow if there are + # many matches that require deduplication. We drop everything in an unsorted + # group without deduplication to avoid this cost. We deduplicate everything manually + # at a later stage. + $_z4h_orig_compadd -V- -2 -o nosort -J- "$@" || return + + emulate -L zsh + typeset -g _z4h_curcontext=$curcontext + if (( $#_z4h_D < $#_z4h_A )); then + _z4h_D+=(${_z4h_A:$#_z4h_D}) + elif (( $#_z4h_D > $#_z4h_A )); then + _z4h_D[$#_z4h_A+1,-1]=() + fi + _z4h_words+=("${_z4h_A[@]}") + _z4h_descrs+=("${_z4h_D[@]}") + if (( ! $#_z4h_U )); then + _z4h_i=(-i "$IPREFIX$_z4h_i[2]") + _z4h_I=(-I "$_z4h_I[2]$ISUFFIX") + fi + local scaffold=( + "$_z4h_in_path_files" + "$_z4h_P[2]" "$_z4h_S[2]" "$_z4h_p[2]" "$_z4h_s[2]" "$_z4h_i[2]" "$_z4h_I[2]" + "$_z4h_q[1]" "$_z4h_r[2]" "$_z4h_R[2]" "$_z4h_f[1]" "$_z4h_e[1]" "$_z4h_W[2]" + "$PREFIX" "$SUFFIX") + scaffold=${(pj:\1:)scaffold} + if (( ! $#_z4h_scaffolds )); then + _z4h_scaffolds=($scaffold) + if [[ $IPREFIX$PREFIX == $_z4h_i[2]$_z4h_P[2]$_z4h_p[2]* ]]; then + _z4h_word_prefix=${${:-$IPREFIX$PREFIX}#$_z4h_i[2]$_z4h_P[2]$_z4h_p[2]} + fi + elif (( $#_z4h_scaffolds > 1 )) || [[ $_z4h_scaffolds[1] != $scaffold ]]; then + repeat $(( $#_z4h_words - $#_z4h_scaffolds - $#_z4h_A )); do + _z4h_scaffolds+=($_z4h_scaffolds[1]) + done + repeat $#_z4h_A _z4h_scaffolds+=($scaffold) + fi + } + + -z4h-cursor-hide + + local ZLE_REMOVE_SUFFIX_CHARS ZLE_SPACE_SUFFIX_CHARS + unset ZLE_REMOVE_SUFFIX_CHARS ZLE_SPACE_SUFFIX_CHARS + + local -i again=1 + while (( again )); do + { + -z4h-show-dots $LBUFFER + + local buf=$BUFFER + local -i cursor=CURSOR + + local -a _z4h_words=() + local -a _z4h_descrs=() + local -a _z4h_scaffolds=() + local -i _z4h_only_dirs=1 + local -i _z4h_in_path_files=0 + local -i _z4h_in_quotes=0 + local _z4h_curcontext= + local _z4h_word_prefix= + + functions -c -- -z4h-main-complete _main_complete + builtin zle expand-or-complete + if [[ $buf == $BUFFER && $cursor == $CURSOR ]]; then + (( ${#_z4h_words} )) || return 0 + local _z4h_path_prefix= + if () { + emulate -L zsh + (( $#_z4h_scaffolds == 1 && !_z4h_in_quotes )) || return + local -a s=("${(@ps:\1:)_z4h_scaffolds}") + [[ $s[1] == 1 && -z $s[3] && -z $s[5] && -z $s[7] && -n $s[11] && + $s[13] == (|*/) && -z $s[15] ]] || return + typeset -g _z4h_path_prefix=$s[13] + }; then + local -a _z4h_reply=() + if builtin zstyle -t ":completion:${_z4h_curcontext}:default" sort; then + _z4h_words=("${(@ou)_z4h_words}") + else + _z4h_words=("${(@u)_z4h_words}") + fi + -z4h-sanitize-word-prefix "${_z4h_words[@]}" + -z4h-comp-files || return + () { + emulate -L zsh + again=${_z4h_reply[1]} + _z4h_words=("${(@q)_z4h_reply:1}") + } + -z4h-insert-all || return + else + local -a _z4h_reply=() + local nl=$'\n' + _z4h_descrs=("${(@)_z4h_descrs//$nl/\\n}") + -z4h-sanitize-word-prefix "${_z4h_descrs[@]}" + -z4h-comp-words || return + () { + emulate -L zsh + again=${_z4h_reply[1]} + local -i idx=0 + local words=() + if (( ${#_z4h_scaffolds} == 1 )); then + for idx in "${(@)_z4h_reply:1}"; do + words+=("${_z4h_words[idx]}") + done + else + local scaffolds=() + for idx in "${(@)_z4h_reply:1}"; do + words+=("${_z4h_words[idx]}") + scaffolds+=("${_z4h_scaffolds[idx]}") + done + _z4h_scaffolds=("${scaffolds[@]}") + fi + _z4h_words=("${words[@]}") + } + -z4h-insert-all || return + fi + else + if (( auto_menu )); then + _z4h_words=("${(@ou)_z4h_words}") + again=$((${#_z4h_words} > 1)) + else + again=0 + fi + fi + + if [[ $LBUFFER == *' ' && $RBUFFER == ' '* ]]; then + RBUFFER[1]= + else + BUFFER=$BUFFER + fi + + [[ $BUFFER == $buf && $CURSOR == $cursor ]] && return + builtin zle .split-undo || return + [[ $LBUFFER == *' ' ]] && return + } always { + if (( _z4h_use[zsh-autosuggestions] || _z4h_use[zsh-syntax-highlighting] )); then + -z4h-redraw-buffer + fi + } + done +} always { + # unsetopt xtrace + # exec 2>&3 3>&- + local f= + for f in $_z4h_shadow_funcs; do + if (( ${+functions[-z4h-orig-$f]} )); then + builtin functions -c -- -z4h-orig-$f $f + builtin unfunction -- -z4h-orig-$f + elif (( ${+functions[$f]} )); then + builtin unfunction -- $f + fi + done + builtin zle -R + -z4h-cursor-show +} diff --git a/fn/z4h-fzf-dir-history b/fn/z4h-fzf-dir-history new file mode 100644 index 0000000..292367a --- /dev/null +++ b/fn/z4h-fzf-dir-history @@ -0,0 +1,176 @@ +#!/usr/bin/env zsh + +[[ -v _z4h_tty_fd ]] || return + +autoload +X -Uz -- -z4h-present-files -z4h-cursor-show -z4h-find -z4h-fzf + +local -i dot_glob list_types +[[ -o dot_glob ]] && dot_glob=1 +[[ -o list_types ]] && list_types=1 + +-z4h-set-list-colors :complete:cd:: $list_types +local -i list_colors=$((!$?)) + +(( ${+_z4h_dir_hist_fd} )) && -z4h-update-dir-history + +{ + -z4h-cursor-hide + + local -i first=1 + while true; do + local -i redraw=0 again=0 + () { + eval "$_z4h_opt" + + if (( first )); then + local -i num_dirs=$#_z4h_dir_history + local -i num_non_empty=0 + else + if (( dot_glob )); then + local dirs=(*(-/DN)) + else + local dirs=(*(-/N)) + fi + local non_empty=(${^${dirs}}/*(D-/Y1N:h:t)) + (( $#dirs )) || return 0 + local -i num_dirs=$#dirs + local -i num_non_empty=$#non_empty + fi + + { + -z4h-show-dots '' + + local -i pct=60 + (( _z4h_can_save_restore_screen )) && pct=100 + + local -i height=$(( ! num_non_empty && 100 * (num_dirs + 4) < pct * LINES ? num_dirs + 4 : pct * LINES / 100 )) + + (( height >= 6 )) || (( height = 6 )) + (( height <= LINES - 1 )) || (( height = LINES - 1 )) + + local opts=( + --color=hl:201,hl+:201 + --with-nth=2 + --delimiter='\000' + --ansi + --exact + --no-mouse + --tiebreak=length,begin,index + --no-multi + --border=horizontal + ) + + if (( first )); then + opts+=(--no-sort) + else + () { + emulate -L zsh + # Set dot_glob in case the value of find-flags depends on it (via `zstyle -e`). + # Ideally we should run this with user options. + (( dot_glob )) && setopt dot_glob + local -a bin + zstyle -a :z4h:${WIDGET#z4h-} find-command bin + if (( ! $#bin )) && (( $+commands[bfs] )); then + opts+=(--no-sort) + fi + } + fi + + local cursor_y cursor_x + -z4h-get-cursor-pos || return + + if (( _z4h_can_save_restore_screen )); then + opts+=(--no-clear) + if { (( height <= cursor_y - 1 )) && zstyle -T :z4h: prompt-at-bottom } || + (( cursor_y - 1 > LINES - cursor_y && cursor_y - 1 > 4 )) && + { (( height > LINES - cursor_y )) || zstyle -T :z4h: prompt-at-bottom }; then + (( height <= cursor_y - 1 )) || (( height = cursor_y - 1 )) + local move=$'\e[0m\e['$((cursor_y-height))';1H' + opts+=(--layout=default) + elif (( LINES - cursor_y > 4 )); then + (( height <= LINES - cursor_y )) || (( height = LINES - cursor_y )) + local move=$'\e[0m\n\r' + opts+=(--layout=reverse) + else + local -i extra=$((height - LINES + cursor_y)) + print -rnu $_z4h_tty_fd -- ${(pl:$height::\n:)} || return + (( cursor_y += LINES - cursor_y - height )) + local move=$'\e[0m\e['$((cursor_y+1))';1H' + opts+=(--layout=reverse) + fi + local _z4h_saved_screen + -z4h-save-screen || return + else + print -u $_z4h_tty_fd || return + local move= + opts+=(--layout=reverse) + fi + + opts+=(--height=$height) + + { + local choice + choice=$( + unsetopt pipe_fail + exec 2>/dev/null + { + print -r -- $sysparams[pid] + if (( first )); then + local dir + for dir in $_z4h_dir_history; do + { + # There is a bug in zsh that triggers if these two + # expansions are combined: ${(g:oce:)${~dir}}. + dir=${~dir} + if [[ -d ${dir::=${(g:oce:)dir}} && $dir != *$'\n'* ]]; then + print -r -- $dir + fi + } always { + TRY_BLOCK_ERROR=0 + } + done 2>/dev/null + else + print -rC1 -- $dirs + -z4h-find $dot_glob 1 $non_empty | command sed -n '/\/.*\// s/^..//p' + fi + } | { + local -a pids + IFS=' ' builtin read -rA pids || exit + print -r -- $pids $sysparams[pid] || exit + -z4h-present-files $list_colors $list_types 1 + } | { + local -a pids + IFS=' ' builtin read -rA pids || pids=() + print -rnu $_z4h_tty_fd -- $move + -z4h-cursor-show + 2>&$_z4h_tty_fd -z4h-fzf $opts + (( $#pids )) && builtin kill -- $pids + } always { + -z4h-cursor-hide + }) + [[ -n $choice ]] || return + choice=("${(@f)choice}") + [[ -n $choice[1] ]] && again=1 + # Weird chars don't roundtrip, so the directory may not exist. + cd -- ${choice[2]%%$'\0'*} 2>/dev/null || return + redraw=1 + } always { + if (( _z4h_can_save_restore_screen )); then + -z4h-restore-screen + print -rn -- $'\e[0m\e['$cursor_y';'$cursor_x'H' + else + builtin echoti cuu 1 + (( cursor_x > 1 )) && builtin echoti cuf $((cursor_x-1)) + fi + } + } always { + zle -R + } + } || return + (( redraw )) && -z4h-redraw-prompt 1 + (( again )) || break + first=0 + done +} always { + -z4h-cursor-show +} diff --git a/fn/z4h-fzf-history b/fn/z4h-fzf-history new file mode 100644 index 0000000..42c3f54 --- /dev/null +++ b/fn/z4h-fzf-history @@ -0,0 +1,170 @@ +#!/usr/bin/env zsh + +# fzf-history-widget with duplicate removal, preview and syntax highlighting (requires `bat`). +# +# Disable preview: +# +# zstyle :z4h:fzf-history fzf-flags --no-preview + +eval "$_z4h_opt" + +[[ -v _z4h_tty_fd ]] || return + +local preview='printf "%s" {} | command cut -f2- -d'$'\1' +(( $+commands[bat] )) && preview+=' | command bat -l bash --color always -pp --wrap=character --terminal-width=$((FZF_PREVIEW_COLUMNS-1))' + +{ + -z4h-show-dots '' + + autoload +X -Uz -- -z4h-cursor-show + + local cursor_y cursor_x + -z4h-get-cursor-pos || return + + local query=${(j: :)${(@Z:cn:)BUFFER}} + [[ -n $query ]] && query+=' ' + + if (( _z4h_can_save_restore_screen )) && zstyle -T :z4h:fzf-history fzf-preview; then + local opts=( + --read0 + --no-multi + --no-sort + --cycle + --exact + --no-mouse + --tabstop 1 + --query=$query + --color=hl:201,hl+:201 + --no-clear + --layout=default + --preview-window=wrap:4:down:noborder + --preview=$preview + --height=$LINES + ) + local move=$'\e[0m\e[H' + local _z4h_saved_screen + -z4h-save-screen || return + else + # A big chunk of this branch is dead code because _z4h_can_save_restore_screen is false. + # I'm keeping it for a while in case I decide to change this logic. + local -i pct=80 + (( _z4h_can_save_restore_screen )) && pct=100 + + local -i space + if (( _z4h_can_save_restore_screen )); then + (( space = LINES - cursor_y < cursor_y - 1 ? cursor_y - 1 : LINES - cursor_y )) + else + (( space = LINES - 1 )) + fi + (( space <= pct * LINES / 100 )) || (( space = pct * LINES / 100 )) + + local opts=( + --read0 + --no-multi + --no-sort + --cycle + --exact + --no-mouse + --tabstop 1 + --query=$query + --color=hl:201,hl+:201 + ) + + local -i preview_lines=6 + local -i height=$(($#history + 2)) + (( height >= 4 )) || (( height = 4 )) + local preview_window=wrap:$preview_lines + if (( height + preview_lines <= space || space > preview_lines + 5 )) && + zstyle -T :z4h:fzf-history fzf-preview; then + (( height += preview_lines )) + else + preview_lines=0 + fi + + (( height <= pct * LINES / 100 )) || (( height = pct * LINES / 100 )) + (( height <= LINES - 1 )) || (( height = LINES - 1 )) + + if (( _z4h_can_save_restore_screen )); then + opts+=(--no-clear) + if { (( height <= cursor_y - 1 )) && zstyle -T :z4h: prompt-at-bottom } || + (( cursor_y - 1 > LINES - cursor_y && cursor_y - 1 > 5 + preview_lines )) && + { (( height > LINES - cursor_y )) || zstyle -T :z4h: prompt-at-bottom }; then + preview_window+=':up' + (( preview_lines )) || opts+=(--border=horizontal) + (( height <= cursor_y - 1 )) || (( height = cursor_y - 1 )) + local move=$'\e[0m\e['$((cursor_y-height))';1H' + opts+=(--layout=default) + elif (( LINES - cursor_y > 5 + preview_lines )); then + preview_window+=':down' + (( height <= LINES - cursor_y )) || (( height = LINES - cursor_y )) + local move=$'\e[0m\n\r' + opts+=(--layout=reverse) + else + preview_window+=':down' + local -i extra=$((height - LINES + cursor_y)) + print -rnu $_z4h_tty_fd -- ${(pl:$height::\n:)} || return + (( cursor_y += LINES - cursor_y - height )) + local move=$'\e[0m\e['$((cursor_y+1))';1H' + opts+=(--layout=reverse) + fi + local _z4h_saved_screen + -z4h-save-screen || return + else + print -u $_z4h_tty_fd || return + preview_window+=':down' + local move= + opts+=(--layout=reverse) + fi + + opts+=(--height=$height) + if (( preview_lines )); then + opts+=( + --preview-window=$preview_window + --preview=$preview + ) + fi + fi + + { + local choice + choice=$( + unsetopt pipe_fail + { + # This `noglob` is a workaround for a bug in zsh that can be + # triggered by certain corrupted history files. + # + # https://github.com/romkatv/zsh4humans/issues/313 + (( $#history )) && noglob printf '%s\000' "${history[@]}" + } | { + { + print -rnu $_z4h_tty_fd -- $move + -z4h-cursor-show + 2>&$_z4h_tty_fd -z4h-fzf $opts + } always { + -z4h-cursor-hide + } + }) + [[ -n $choice ]] || return + BUFFER=${choice#*$'\n'} + typeset -gi CURSOR='_z4h_cursor_max()' + if (( _z4h_use[zsh-autosuggestions] )); then + typeset -g _z4h_autosuggest_buffer=$BUFFER + unset _z4h_autosuggestion POSTDISPLAY + fi + if (( _z4h_use[zsh-autosuggestions] || _z4h_use[zsh-syntax-highlighting] )); then + -z4h-redraw-buffer + fi + return 0 + } always { + if (( _z4h_can_save_restore_screen )); then + -z4h-restore-screen + print -rn -- $'\e[0m\e['$cursor_y';'$cursor_x'H' + else + builtin echoti cuu 1 + (( cursor_x > 1 )) && builtin echoti cuf $((cursor_x-1)) + fi + } +} always { + zle -R + -z4h-cursor-show +} diff --git a/fn/z4h-quote-prev-zword b/fn/z4h-quote-prev-zword new file mode 100644 index 0000000..2f5f080 --- /dev/null +++ b/fn/z4h-quote-prev-zword @@ -0,0 +1,73 @@ +#!/usr/bin/env zsh +# +# Quote the shell word to the left of the cursor with double or single quotes. +# Prefer quotes with the shorter result or double quotes if there is no +# difference. If already quoted, change quotes. If there is an unterminated +# quote, terminate it and quote the resulting word with the same kind of quotes. +# Keep the cursor on the content it was pointing to before the widget was +# invoked. +# +# Here are a few examples. The caret shows the position of the cursor. +# *Initial* is the content of the buffer before the widget is executed. +# *Once* is the content of the buffer after the widget is executed. +# *Twice* is the content of the buffer after the widget is executed twice. +# +# | Initial | Once | Twice | Note +# |----------|------------|------------|------------------- +# | foo | "foo" | 'foo' | prefer double quotes +# | ^ | ^ | ^ | +# | foo | "foo" | 'foo' | keep cursor on the same word char +# | ^ | ^ | ^ | +# | foo | "foo" | 'foo' | +# | ^ | ^ | ^ | +# | foo bar | "foo" bar | 'foo' bar | cursor on a space => quote prev word +# | ^ | ^ | ^ | +# | foo bar | foo "bar" | foo 'bar' | +# | ^ | ^ | ^ | +# | "foo" | 'foo' | "foo" | replace quotes if already quoted +# | ^ | ^ | ^ | +# | 'foo' | "foo" | 'foo' | replace quotes if already quoted +# | ^ | ^ | ^ | +# | foo\" | 'foo"' | "foo\"" | single quotes because it's shorter +# | ^ | ^ | ^ | +# | "foo | "foo" | 'foo' | +# | ^ | ^ | ^ | +# | 'foo | 'foo' | "foo" | single quotes to match +# | ^ | ^ | ^ | +# | foo" | "foo" | 'foo' | +# | ^ | ^ | ^ | +# | foo' | 'foo' | "foo" | +# | ^ | ^ | ^ | +# | foo" | "foo" | 'foo' | +# | ^ | ^ | ^ | + +emulate -L zsh + +local reply +-z4h-find-prev-zword || return 0 + +local -i start='reply[1]' +(( start <= 0 )) && return +local -i end='reply[2]' +local word=$BUFFER[start,end] + +local -i q +if -z4h-is-valid-list ": $word'"; then + word+="'" + local -i q=1 +elif -z4h-is-valid-list ": $word\""; then + word+='"' + local -i q=2 +fi + +local unquoted=${(Q)word} +local quoted=(${(qq)unquoted} ${(qqq)unquoted}) +if (( ! q )); then + if [[ $quoted[1] != $word ]] && (( $#quoted[1] < $#quoted[2] )) || + [[ $quoted[2] == $word ]]; then + (( q = 1 )) + else + (( q = 2 )) + fi +fi +-z4h-replace-buf $start $end $quoted[q] diff --git a/fn/z4h-stash-buffer b/fn/z4h-stash-buffer new file mode 100644 index 0000000..559b4bc --- /dev/null +++ b/fn/z4h-stash-buffer @@ -0,0 +1,6 @@ +#!/usr/bin/env zsh + +eval "$_z4h_opt" +[[ -z $BUFFER ]] && return +fc -R =(print -r -- ${BUFFER//$'\n'/$'\\\n'}) +BUFFER= diff --git a/main.zsh b/main.zsh new file mode 100644 index 0000000..522640a --- /dev/null +++ b/main.zsh @@ -0,0 +1,480 @@ +if '[' '-z' "${ZSH_VERSION-}" ']' || ! 'eval' '[[ "$ZSH_VERSION" == (5.<8->*|<6->.*) ]]'; then + '.' "$Z4H"/zsh4humans/sc/exec-zsh-i || 'return' +fi + +if [[ -x /proc/self/exe ]]; then + typeset -gr _z4h_exe=${${:-/proc/self/exe}:A} +else + () { + emulate zsh -o posix_argzero -c 'local exe=${0#-}' + if [[ $SHELL == /* && ${SHELL:t} == $exe && -x $SHELL ]]; then + exe=$SHELL + elif (( $+commands[$exe] )); then + exe=$commands[$exe] + elif [[ -x $exe ]]; then + exe=${exe:a} + else + print -Pru2 -- "%F{3}z4h%f: unable to find path to %F{1}zsh%f" + return 1 + fi + typeset -gr _z4h_exe=${exe:A} + } || return +fi + +if ! { zmodload -s zsh/terminfo zsh/zselect && [[ -n $^fpath/compinit(#qN) ]] || + [[ $ZSH_PATCHLEVEL == zsh-5.8-0-g77d203f && $_z4h_exe == */bin/zsh && + -e ${_z4h_exe:h:h}/share/zsh/5.8/scripts/relocate ]] }; then + builtin source $Z4H/zsh4humans/sc/exec-zsh-i || return +fi + +if [[ ! -o interactive ]]; then + # print -Pru2 -- "%F{3}z4h%f: starting interactive %F{2}zsh%f" + # This is caused by Z4H_BOOTSTRAPPING, so we don't need to consult ZSH_SCRIPT and the like. + exec -- $_z4h_exe -i || return +fi + +typeset -gr _z4h_opt='emulate -L zsh && + setopt typeset_silent pipe_fail extended_glob prompt_percent no_prompt_subst && + setopt no_prompt_bang no_bg_nice no_aliases' + +zmodload zsh/{datetime,langinfo,parameter,system,terminfo,zutil} || return +zmodload -F zsh/files b:{zf_mkdir,zf_mv,zf_rm,zf_rmdir,zf_ln} || return +zmodload -F zsh/stat b:zstat || return + +() { + if [[ $1 != $Z4H/zsh4humans/main.zsh ]]; then + print -Pru2 -- "%F{3}z4h%f: confusing %Umain.zsh%u location: %F{1}${1//\%/%%}%f" + return 1 + fi + if (( _z4h_zle )); then + typeset -gr _z4h_param_pat=$'ZDOTDIR=$ZDOTDIR\0Z4H=$Z4H\0Z4H_URL=$Z4H_URL' + typeset -gr _z4h_param_sig=${(e)_z4h_param_pat} + function -z4h-check-core-params() { + [[ "${(e)_z4h_param_pat}" == "$_z4h_param_sig" ]] || { + -z4h-error-param-changed + return 1 + } + } + else + function -z4h-check-core-params() {} + fi +} ${${(%):-%x}:a} || return + +export -T MANPATH=${MANPATH:-:} manpath +export -T INFOPATH=${INFOPATH:-:} infopath +typeset -gaU cdpath fpath mailpath path manpath infopath + +function -z4h-init-homebrew() { + (( ARGC )) || return 0 + local dir=${1:h:h} + export HOMEBREW_PREFIX=$dir + export HOMEBREW_CELLAR=$dir/Cellar + if [[ -e $dir/Homebrew/Library ]]; then + export HOMEBREW_REPOSITORY=$dir/Homebrew + else + export HOMEBREW_REPOSITORY=$dir + fi +} + +if [[ $OSTYPE == darwin* ]]; then + if [[ ! -e $Z4H/cache/init-darwin-paths ]] || ! source $Z4H/cache/init-darwin-paths; then + autoload -Uz $Z4H/zsh4humans/fn/-z4h-gen-init-darwin-paths + -z4h-gen-init-darwin-paths && source $Z4H/cache/init-darwin-paths + fi + [[ -z $HOMEBREW_PREFIX ]] && -z4h-init-homebrew {/opt/homebrew,/usr/local}/bin/brew(N) +elif [[ $OSTYPE == linux* && -z $HOMEBREW_PREFIX ]]; then + -z4h-init-homebrew {/home/linuxbrew/.linuxbrew,~/.linuxbrew}/bin/brew(N) +fi + +fpath=( + ${^${(M)fpath:#*/$ZSH_VERSION/functions}/%$ZSH_VERSION\/functions/site-functions}(-/N) + ${HOMEBREW_PREFIX:+$HOMEBREW_PREFIX/share/zsh/site-functions}(-/N) + /opt/homebrew/share/zsh/site-functions(-/N) + /usr{/local,}/share/zsh/{site-functions,vendor-completions}(-/N) + $fpath + $Z4H/zsh4humans/fn) + +autoload -Uz -- $Z4H/zsh4humans/fn/(|-|_)z4h[^.]#(:t) || return +functions -Ms _z4h_err + +() { + path=(${@:|path} $path /snap/bin(-/N)) +} {~/bin,~/.local/bin,~/.cargo/bin,${HOMEBREW_PREFIX:+$HOMEBREW_PREFIX/bin},${HOMEBREW_PREFIX:+$HOMEBREW_PREFIX/sbin},/opt/local/sbin,/opt/local/bin,/usr/local/sbin,/usr/local/bin}(-/N) + +() { + manpath=(${@:|manpath} "${manpath[@]}" '') +} {${HOMEBREW_PREFIX:+$HOMEBREW_PREFIX/share/man},/opt/local/share/man}(-/N) + +() { + infopath=(${@:|infopath} $infopath '') +} {${HOMEBREW_PREFIX:+$HOMEBREW_PREFIX/share/info},/opt/local/share/info}(-/N) + +if [[ $ZSH_PATCHLEVEL == zsh-5.8-0-g77d203f && $_z4h_exe == */bin/zsh && + -e ${_z4h_exe:h:h}/share/zsh/5.8/scripts/relocate ]]; then + if [[ $TERMINFO != ~/.terminfo && $TERMINFO != ${_z4h_exe:h:h}/share/terminfo && + -e ${_z4h_exe:h:h}/share/terminfo/$TERM[1]/$TERM ]]; then + export TERMINFO=${_z4h_exe:h:h}/share/terminfo + fi + if [[ -e ${_z4h_exe:h:h}/share/man ]]; then + manpath=(${_z4h_exe:h:h}/share/man $manpath '') + fi +fi + +path+=($Z4H/fzf/bin) +manpath+=($Z4H/fzf/man) + +: ${GITSTATUS_CACHE_DIR=$Z4H/cache/gitstatus} +: ${ZSH=$Z4H/ohmyzsh/ohmyzsh} +: ${ZSH_CUSTOM=$Z4H/ohmyzsh/ohmyzsh/custom} +: ${ZSH_CACHE_DIR=$Z4H/cache/ohmyzsh} + +[[ $terminfo[Tc] == yes && -z $COLORTERM ]] && export COLORTERM=truecolor + +if [[ $EUID == 0 && -z ~(#qNU) && $Z4H == ~/* ]]; then + typeset -gri _z4h_dangerous_root=1 +else + typeset -gri _z4h_dangerous_root=0 +fi + +[[ $langinfo[CODESET] == (utf|UTF)(-|)8 ]] || -z4h-fix-locale + +function -z4h-cmd-source() { + local _z4h_file _z4h_compile + zparseopts -D -F -- c=_z4h_compile -compile=_z4h_compile || return '_z4h_err()' + emulate zsh -o extended_glob -c 'local _z4h_files=(${^${(M)@:#/*}}(N) $Z4H/${^${@:#/*}}(N))' + if (( ${#_z4h_compile} )); then + builtin set -- + for _z4h_file in "${_z4h_files[@]}"; do + -z4h-compile "$_z4h_file" || true + builtin source -- "$_z4h_file" + done + else + emulate zsh -o extended_glob -c 'local _z4h_rm=(${^${(@)_z4h_files:#$Z4H/*}}.zwc(N))' + (( ! ${#_z4h_rm} )) || zf_rm -f -- "${_z4h_rm[@]}" || true + builtin set -- + for _z4h_file in "${_z4h_files[@]}"; do + builtin source -- "$_z4h_file" + done + fi +} + +function -z4h-cmd-load() { + local -a compile + zparseopts -D -F -- c=compile -compile=compile || return '_z4h_err()' + + local -a files + + () { + emulate -L zsh -o extended_glob + local pkgs=(${(M)@:#/*} $Z4H/${^${@:#/*}}) + pkgs=(${^${(u)pkgs}}(-/FN)) + local dirs=(${^pkgs}/functions(-/FN)) + local funcs=(${^dirs}/^([_.]*|prompt_*_setup|README*|*~|*.zwc)(-.N:t)) + fpath+=($pkgs $dirs) + (( $#funcs )) && autoload -Uz -- $funcs + local dir + for dir in $pkgs; do + if [[ -s $dir/init.zsh ]]; then + files+=($dir/init.zsh) + elif [[ -s $dir/${dir:t}.plugin.zsh ]]; then + files+=($dir/${dir:t}.plugin.zsh) + fi + done + } "$@" + + -z4h-cmd-source "${compile[@]}" -- "${files[@]}" +} + +function -z4h-cmd-init() { + if (( ARGC )); then + print -ru2 -- ${(%):-"%F{3}z4h%f: unexpected %F{1}init%f argument"} + return '_z4h_err()' + fi + if (( ${+_z4h_init_called} )); then + if [[ ${funcfiletrace[-1]} != zsh:0 ]]; then + if '[' "${ZDOTDIR:-$HOME}" '=' "$HOME" ']'; then + >&2 'printf' '\033[33mz4h\033[0m: please use \033[4;32mexec\033[0m \033[32mzsh\033[0m instead of \033[32msource\033[0m \033[4m~/.zshrc\033[0m\n' + else + >&2 'printf' '\033[33mz4h\033[0m: please use \033[4;32mexec\033[0m \033[32mzsh\033[0m instead of \033[32msource\033[0m \033[4;33m"$ZDOTDIR"\033[0;4m/.zshrc\033[0m\n' + fi + 'return' '1' + fi + print -ru2 -- ${(%):-"%F{3}z4h%f: %F{1}init%f cannot be called more than once"} + return '_z4h_err()' + fi + -z4h-check-core-params || return + typeset -gri _z4h_init_called=1 + + () { + eval "$_z4h_opt" + + (( _z4h_dangerous_root || $+Z4H_SSH )) || + ! zstyle -T :z4h: chsh || + [[ ${SHELL-} == $_z4h_exe || ${SHELL-} -ef $_z4h_exe || -e $Z4H/stickycache/no-chsh ]] || + -z4h-chsh || + true + + local -a start_tmux + local -i install_tmux need_restart + if [[ -n $MC_TMPDIR ]]; then + start_tmux=(no) + else + # 'integrated', 'isolated', 'system', or 'command' [arg]... + zstyle -a :z4h: start-tmux start_tmux || start_tmux=(isolated) + if (( $#start_tmux == 1 )); then + case $start_tmux[1] in + integrated|isolated) install_tmux=1;; + system) start_tmux=(command tmux -u);; + esac + fi + fi + + if [[ -n $_Z4H_TMUX_TTY && $_Z4H_TMUX_TTY != $TTY ]]; then + [[ $TMUX == $_Z4H_TMUX ]] && unset TMUX TMUX_PANE + unset _Z4H_TMUX _Z4H_TMUX_PANE _Z4H_TMUX_CMD _Z4H_TMUX_TTY + elif [[ -n $_Z4H_TMUX_CMD ]]; then + install_tmux=1 + fi + + if ! [[ _z4h_zle -eq 1 && -o zle && -t 0 && -t 1 && -t 2 ]]; then + unset _Z4H_TMUX _Z4H_TMUX_PANE _Z4H_TMUX_CMD _Z4H_TMUX_TTY + else + local tmux=$Z4H/tmux/bin/tmux + local -a match mbegin mend + if [[ $TMUX == (#b)(/*),(|<->),(|<->) && -w $match[1] ]]; then + if [[ $TMUX == */z4h-tmux-* ]]; then + export _Z4H_TMUX=$TMUX + export _Z4H_TMUX_PANE=$TMUX_PANE + export _Z4H_TMUX_CMD=$tmux + export _Z4H_TMUX_TTY=$TTY + unset TMUX TMUX_PANE + elif [[ -x /proc/$match[2]/exe ]]; then + export _Z4H_TMUX=$TMUX + export _Z4H_TMUX_PANE=$TMUX_PANE + export _Z4H_TMUX_CMD=/proc/$match[2]/exe + export _Z4H_TMUX_TTY=$TTY + elif (( $+commands[tmux] )); then + export _Z4H_TMUX=$TMUX + export _Z4H_TMUX_PANE=$TMUX_PANE + export _Z4H_TMUX_CMD=$commands[tmux] + export _Z4H_TMUX_TTY=$TTY + else + unset _Z4H_TMUX _Z4H_TMUX_PANE _Z4H_TMUX_CMD _Z4H_TMUX_TTY + fi + if [[ -n $_Z4H_TMUX && -t 1 ]] && + zstyle -T :z4h: prompt-at-bottom && + ! zselect -t0 -r 0; then + local cursor_y cursor_x + -z4h-get-cursor-pos 1 || cursor_y=0 + local -i n='LINES - cursor_y' + print -rn -- ${(pl:$n::\n:)} + fi + elif (( install_tmux )) && + [[ -z $TMUX && ! -w ${_Z4H_TMUX%,(|<->),(|<->)} && -z $Z4H_SSH ]]; then + unset _Z4H_TMUX _Z4H_TMUX_PANE _Z4H_TMUX_CMD _Z4H_TMUX_TTY TMUX TMUX_PANE + if [[ -x $tmux && -d $Z4H/terminfo ]]; then + # We prefer /tmp over $TMPDIR because the latter breaks rendering + # of wide chars on iTerm2. + local sock + if [[ -n $TMUX_TMPDIR && -d $TMUX_TMPDIR && -w $TMUX_TMPDIR ]]; then + sock=$TMUX_TMPDIR + elif [[ -d /tmp && -w /tmp ]]; then + sock=/tmp + elif [[ -n $TMPDIR && -d $TMPDIR && -w $TMPDIR ]]; then + sock=$TMPDIR + fi + if [[ -n $sock ]]; then + local tmux_suf + local -a cmds=() + sock=${sock%/}/z4h-tmux-$UID + if (( terminfo[colors] >= 256 )); then + cmds+=(set -g default-terminal tmux-256color ';') + if [[ $COLORTERM == (24bit|truecolor) ]]; then + cmds+=(set -ga terminal-features ',*:RGB:usstyle:overline' ';') + sock_suf+='-tc' + fi + else + cmds+=(set -g default-terminal screen ';') + fi + if zstyle -t :z4h: term-vresize top; then + cmds+=(set -g history-limit 1024 ';') + sock_suf+='-h' + fi + if [[ $start_tmux[1] == isolated ]]; then + sock+=-$sysparams[pid] + else + sock+=-$TERM$sock_suf + if [[ -e $Z4H/tmux/stamp ]]; then + # Append a unique per-installation number to the socket path to work + # around a bug in tmux. See https://github.com/romkatv/zsh4humans/issues/71. + local stamp + IFS= read -r stamp <$Z4H/tmux/stamp || return + sock+=-${stamp%%.*} + fi + fi + if zstyle -t :z4h: propagate-cwd && [[ -n $TTY && $TTY != *(.| )* ]]; then + if [[ $PWD == /* && $PWD -ef . ]]; then + local orig_dir=$PWD + else + local orig_dir=${${:-.}:a} + fi + if [[ -n "$TMPDIR" && ( ( -d "$TMPDIR" && -w "$TMPDIR" ) || ! ( -d /tmp && -w /tmp ) ) ]]; then + local tmpdir=$TMPDIR + else + local tmpdir=/tmp + fi + local dir=$tmpdir/z4h-tmux-cwd-$UID-$$-${TTY//\//.} + { + zf_mkdir -p -- $dir && + print -r -- "TMUX=${(q)sock} TMUX_PANE= ${(q)tmux} "'"$@"' >$dir/tmux && + builtin cd -q -- $dir + } 2>/dev/null + if (( $? )); then + zf_rm -rf -- "$dir" 2>/dev/null + local exec= + else + export _Z4H_ORIG_CWD=$orig_dir + local exec= + fi + else + local exec=exec + fi + SHELL=$_z4h_exe _Z4H_LINES=$LINES _Z4H_COLUMNS=$COLUMNS \ + builtin $exec - $tmux -u -S $sock -f $Z4H/zsh4humans/.tmux.conf -- \ + "${cmds[@]}" new >/dev/null || return + [[ -z $exec ]] || return + builtin cd / + zf_rm -rf -- $dir 2>/dev/null + builtin exit 0 + fi + else + need_restart=1 + fi + elif [[ -z $TMUX && $start_tmux[1] == command ]] && (( $+commands[$start_tmux[2]] )); then + if [[ -d $Z4H/terminfo ]]; then + SHELL=$_z4h_exe exec - ${start_tmux:1} || return + else + need_restart=1 + fi + fi + fi + + if [[ -x /usr/lib/systemd/systemd || -x /lib/systemd/systemd ]]; then + _z4h_install_queue+=(systemd) + fi + local brew + if [[ -n $HOMEBREW_REPOSITORY(#qNU) && + ! -e $HOMEBREW_REPOSITORY/Library/Taps/homebrew/homebrew-command-not-found/cmd/which-formula.rb && + -v commands[brew] ]]; then + brew=homebrew-command-not-found + fi + _z4h_install_queue+=( + zsh-history-substring-search zsh-autosuggestions zsh-completions + zsh-syntax-highlighting terminfo fzf $brew powerlevel10k) + (( install_tmux )) && _z4h_install_queue+=(tmux) + if ! -z4h-install-many; then + [[ -e $Z4H/.updating ]] || -z4h-error-command init + return 1 + fi + if (( _z4h_installed_something )); then + if [[ $TERMINFO != ~/.terminfo && -e ~/.terminfo/$TERM[1]/$TERM ]]; then + export TERMINFO=~/.terminfo + fi + if (( need_restart )); then + print -ru2 ${(%):-"%F{3}z4h%f: restarting %F{2}zsh%f"} + exec -- $_z4h_exe -i || return + else + print -ru2 ${(%):-"%F{3}z4h%f: initializing %F{2}zsh%f"} + export P9K_TTY=old + fi + fi + + if [[ -w $TTY ]]; then + typeset -gi _z4h_tty_fd + sysopen -o cloexec -rwu _z4h_tty_fd -- $TTY || return + typeset -gri _z4h_tty_fd + elif [[ -w /dev/tty ]]; then + typeset -gi _z4h_tty_fd + if sysopen -o cloexec -rwu _z4h_tty_fd -- /dev/tty 2>/dev/null; then + typeset -gri _z4h_tty_fd + else + unset _z4h_tty_fd + fi + fi + + if [[ -v _z4h_tty_fd && (-n $Z4H_SSH && -n $_Z4H_SSH_MARKER || -n $_Z4H_TMUX) ]]; then + typeset -gri _z4h_can_save_restore_screen=1 # this parameter is read by p10k + else + typeset -gri _z4h_can_save_restore_screen=0 # this parameter is read by p10k + fi + + if (( _z4h_zle )) && zstyle -t :z4h:direnv enable && [[ -e $Z4H/cache/direnv ]]; then + -z4h-direnv-init 0 || return '_z4h_err()' + fi + + local rc_zwcs=($ZDOTDIR/{.zshenv,.zprofile,.zshrc,.zlogin,.zlogout}.zwc(N)) + if (( $#rc_zwcs )); then + -z4h-check-rc-zwcs $rc_zwcs || return '_z4h_err()' + fi + + typeset -gr _z4h_orig_shell=${SHELL-} + } || return + + : ${ZLE_RPROMPT_INDENT:=0} + + # Enable Powerlevel10k instant prompt. + (( ! _z4h_zle )) || zstyle -t :z4h:powerlevel10k channel none || () { + local user=${(%):-%n} + local XDG_CACHE_HOME=$Z4H/cache/powerlevel10k + [[ -r $XDG_CACHE_HOME/p10k-instant-prompt-$user.zsh ]] || return 0 + builtin source $XDG_CACHE_HOME/p10k-instant-prompt-$user.zsh + } + + local -i z4h_no_flock + + { + () { + eval "$_z4h_opt" + -z4h-init && return + [[ -e $Z4H/.updating ]] || -z4h-error-command init + return 1 + } + } always { + (( z4h_no_flock )) || setopt hist_fcntl_lock + } +} + +function -z4h-cmd-install() { + eval "$_z4h_opt" + -z4h-check-core-params || return + + local -a flush + zparseopts -D -F -- f=flush -flush=flush || return '_z4h_err()' + + local invalid=("${@:#([^/]##/)##[^/]##}") + if (( $#invalid )); then + print -Pru2 -- '%F{3}z4h%f: %Binstall%b: invalid project name(s)' + print -Pru2 -- '' + print -Prlu2 -- ' %F{1}'${(q)^invalid//\%/%%}'%f' + return 1 + fi + _z4h_install_queue+=("$@") + (( $#flush && $#_z4h_install_queue )) || return 0 + -z4h-install-many && return + -z4h-error-command install + return 1 +} + +# Main zsh4humans function. Type `z4h help` for usage. +function z4h() { + if (( ${+functions[-z4h-cmd-${1-}]} )); then + -z4h-cmd-"$1" "${@:2}" + else + -z4h-cmd-help >&2 + return 1 + fi +} + +[[ ${Z4H_SSH-} != <1->:* ]] || -z4h-ssh-maybe-update || return + +unset KITTY_SHELL_INTEGRATION ITERM_INJECT_SHELL_INTEGRATION diff --git a/sc/exec-zsh-i b/sc/exec-zsh-i new file mode 100644 index 0000000..841f6fe --- /dev/null +++ b/sc/exec-zsh-i @@ -0,0 +1,115 @@ +#!/bin/sh +# +# This file gets sourced. Does not return on success. + +_z4h_try_exec_zsh_i() { + >'/dev/null' 2>&1 command -v "$1" || 'return' '0' + <'/dev/null' >'/dev/null' 2>&1 'command' "$1" '-fc' ' + [[ $ZSH_VERSION == (5.<8->*|<6->.*) ]] || return + exe=${${(M)0:#/*}:-$commands[$0]} + zmodload -s zsh/terminfo zsh/zselect || + [[ $ZSH_PATCHLEVEL == zsh-5.8-0-g77d203f && $exe == */bin/zsh && + -e ${exe:h:h}/share/zsh/5.8/scripts/relocate ]]' || 'return' '0' + # >&2 'printf' '\033[33mz4h\033[0m: starting \033[32mzsh\033[0m\n' + 'exec' "$@" || 'return' +} + +_z4h_exec_zsh_i() { + '_z4h_try_exec_zsh_i' 'zsh' "$@" || 'return' + '_z4h_try_exec_zsh_i' '/usr/local/bin/zsh' "$@" || 'return' + '_z4h_try_exec_zsh_i' ~/'.local/bin/zsh' "$@" || 'return' + '_z4h_try_exec_zsh_i' ~/'.zsh-bin/bin/zsh' "$@" || 'return' + '_z4h_try_exec_zsh_i' "${PREFIX-}"/local/bin/zsh "$@" || 'return' + + if '[' '-r' "$Z4H"/stickycache/zshdir ]; then + 'local' 'dir' + IFS='' 'read' '-r' 'dir' <"$Z4H"/stickycache/zshdir || 'return' + '_z4h_try_exec_zsh_i' "$dir"/bin/zsh "$@" || 'return' + fi + + # There is no suitable Zsh. Need to install. + >&2 'printf' '\033[33mz4h\033[0m: cannot find usable \033[32mzsh\033[0m\n' + >&2 'printf' '\033[33mz4h\033[0m: fetching \033[1mzsh 5.8\033[0m installer\n' + + 'local' 'install' + if command -v 'mktemp' >'/dev/null' 2>&1; then + install="$('command' 'mktemp' "$Z4H"/tmp/install-zsh.XXXXXXXXXX)" || 'return' + else + install="$Z4H"/tmp/install-zsh.tmp."$$" + '[' '!' '-e' "$install" ']' || 'command' 'rm' '-rf' '--' "$install" || 'return' + fi + + 'local' zsh_url='https://raw.githubusercontent.com/romkatv/zsh-bin/master/install' + + ( + 'local' 'err' + if command -v 'curl' >'/dev/null' 2>&1; then + err="$(command curl -fsSL -- "$zsh_url" 2>&1 >"$install")" + elif command -v 'wget' >'/dev/null' 2>&1; then + err="$(command wget -O- -- "$zsh_url" 2>&1 >"$install")" + else + >&2 'printf' '\033[33mz4h\033[0m: please install \033[32mcurl\033[0m or \033[32mwget\033[0m\n' + 'exit' '1' + fi + if '[' "$?" '!=' '0' ']'; then + >&2 'printf' "%s\n" "$err" + >&2 'printf' '\033[33mz4h\033[0m: failed to download \033[31m%s\033[0m\n' "$zsh_url" + 'command' 'rm' '-f' '--' "$install" + 'exit' '1' + fi + ) || 'return' + + if '[' '-n' "${Z4H_SSH-}" '-o' "${USER-}" '=' 'cloudshell-user' ']'; then + >&2 'printf' '\033[33mz4h\033[0m: installing \033[1mzsh 5.8\033[0m to \033[4m~/.local\033[0m\n' + 'command' 'sh' '--' "$install" '-d' ~/'.local' '-e' 'no' '-q' || 'return' + 'local' 'dir'="$HOME"/.local + else + 'local' 'zshdir' 'dir' + if command -v 'mktemp' >'/dev/null' 2>&1; then + zshdir="$('command' 'mktemp' "$Z4H"/tmp/zshdir.XXXXXXXXXX)" || 'return' + else + zshdir="$Z4H"/tmp/zshdir.tmp."$$" + '[' '!' '-e' "$zshdir" ']' || 'command' 'rm' '-rf' '--' "$zshdir" || 'return' + fi + while 'true'; do + >&2 'echo' + if 'command' 'sh' '--' "$install" '-s' '3' 3>"$zshdir"; then + IFS='' 'read' '-r' 'dir' <"$zshdir" || 'return' + 'command' 'rm' '-f' '--' "$Z4H"/stickycache/zshdir || 'return' + 'command' 'mv' '-f' '--' "$zshdir" "$Z4H"/stickycache/zshdir || 'return' + 'break' + fi + >&2 'echo' + >&2 'printf' '\033[33mz4h\033[0m: \033[32mZsh 5.8\033[0m installation \033[31mfailed\033[0m\n' + >&2 'echo' + while 'true'; do + >&2 'printf' 'Try again? [y/N] ' + 'local' yn='' + IFS='' 'read' '-r' 'yn' || yn='n' + case "$yn" in + 'y'|'Y'|'yes'|'YES'|'Yes') 'break';; + 'n'|'N'|'no'|'NO'|'No') 'return' '1';; + esac + done + done + fi + + if ! '_z4h_try_exec_zsh_i' "$dir"/bin/zsh "$@"; then + >&2 'printf' '\033[33mz4h\033[0m: \033[31minternal error\033[0m\n' + 'return' '1' + fi +} + +if '[' '-n' "${ZSH_VERSION-}" ']'; then + # TODO: propagate original options here. Will need to save them similarly + # to _z4h_script_argv. + if '[' "${+ZSH_EXECUTION_STRING}" '=' '1' ']'; then + '_z4h_exec_zsh_i' '-i' '-c' "$ZSH_EXECUTION_STRING" + elif '[' "${+ZSH_SCRIPT}" '=' '1' ']'; then + '_z4h_exec_zsh_i' '-i' '--' "$ZSH_SCRIPT" "${_z4h_script_argv[@]}" + fi +fi +'_z4h_exec_zsh_i' '-i' + +'unset' '-f' '_z4h_try_exec_zsh_i' '_z4h_exec_zsh_i' +'return' '1' diff --git a/sc/install-tmux b/sc/install-tmux new file mode 100755 index 0000000..60ec110 --- /dev/null +++ b/sc/install-tmux @@ -0,0 +1,521 @@ +#!/bin/sh + +{ + +set -ue + +if [ -n "${ZSH_VERSION:-}" ]; then + emulate sh -o err_exit -o no_unset +fi + +readonly url_base=https://github.com/romkatv/tmux-bin/releases/download/v3.1.0 + +readonly archives=' +--------------------- +file:tmux-darwin-arm64.tar.gz; md5:2e312f7555a6395f1d81bfa3b083d710; sha256:134a9afc1184269810af9feae41fdc19bad7d5e0ce90ee7376a22946759d5cd7; +file:tmux-darwin-x86_64.tar.gz; md5:1857c31a650ef7dada3940b3d45b95f1; sha256:a2f359975ac8b471713e82f91700b75049fed82930bf72678d74ea730dbc89e9; +file:tmux-linux-aarch64.tar.gz; md5:de566f3edf53cd1a94555495618d99ff; sha256:a8d0b937e124d9bd8bb312c026fc55bfaec4d41fb0f729a52c50de5222d86594; +file:tmux-linux-armv6l.tar.gz; md5:417ce72afb8f2ff823e75429c9bf7065; sha256:9ddc576b39e35b9186936246bc6e632f2e691be27ea52372eee8bdaf2d51de90; +file:tmux-linux-armv7l.tar.gz; md5:6750b5cb7993c411d88904e6eeaa8c4c; sha256:9fd3918371c48a4795f22b600a646974a312621611a024792d296f69b26aa2af; +file:tmux-linux-i386.tar.gz; md5:3e8a9286e6f80c6e9abad5eaf1d85eea; sha256:b8f62934b3ea4817a32c915a98dcc415a762ff5169f9aa0ec367796f505fb6bb; +file:tmux-linux-i586.tar.gz; md5:7bced5f298d8c59c7638e22b1e1ae6ae; sha256:59b53cc3331c84eee46bd79b55b4289a8ac81853f53233e880bc057e45646620; +file:tmux-linux-i686.tar.gz; md5:2a411bd0737b00326509da8ba4cf26e0; sha256:520898915c5575da0f06abf89cc044f35f428828de2fa6956ce786fb53d983f7; +file:tmux-linux-x86_64.tar.gz; md5:6aa7a656836540efadaf4b45ac7312fe; sha256:2e16b79a6638c9b450eb7a807696ea21395761f2d9a7ee8036d1a396be3cf643;' + +readonly lf=" +" + +if [ -t 1 ]; then + readonly _0="$(printf '\033[0m')" + readonly _B="$(printf '\033[1m')" + readonly _U="$(printf '\033[4m')" + readonly _R="$(printf '\033[31m')" + readonly _G="$(printf '\033[32m')" + readonly _Y="$(printf '\033[33m')" +else + readonly _0= + readonly _B= + readonly _U= + readonly _R= + readonly _G= + readonly _Y= +fi + +usage="$(cat <]... + [OPTIONS] -f FILE + [OPTIONS] -u URL + +If '-f FILE' is specified, install tmux from the specified *.tar.gz +file produced by the build script. + +If '-u URL' is specified, download the file and install as if with +'-f FILE'. + +If neither '-f' nor '-u' is specified, download the appopriate file +from https://github.com/romkatv/tmux-bin/releases and install as if +with '-f FILE'. If '-a ' is specified at least once, +abort installation if integrity of the downloaded package cannot +be verified with at least one of the listed hashing algorithms. + +Options: + + -q + + Produce no output on success. + + -d DIR + + Install to this directory. If specified more than once, present + an interactive dialog to choose the directory. Empty argument + means a custom directory (requires manual user input). If '-d' + is not specified, the effect is idential to this: + + -d /usr/local -d ~/.local -d "" + + Except on Termux: + + -d "\$PREFIX"/local -d ~/.local -d "" + + -s FD + + On success, write the path to the installation directory to this + file descriptor. +END +)" + +absfile() { + if [ ! -e "$1" ]; then + >&2 echo "${_R}error${_0}: file not found: ${_U}$1${_0}" + return 1 + fi + local dir base + dir="$(dirname -- "$1")" + base="$(basename -- "$1")" + ( cd -- "$dir" && dir="$(pwd)" && printf '%s/%s\n' "${dir%/}" "${base}" ) +} + +check_dir() { + if [ -z "$1" ]; then + >&2 echo "${_R}error${_0}: directory cannot be empty string" + exit 1 + fi + if [ -z "${1##~*}" ]; then + >&2 echo "${_R}error${_0}: please expand ${_U}~${_0} in directory name: ${_U}$1${_0}" + exit 1 + fi + if [ -z "${1##//*}" ]; then + >&2 echo "${_R}error${_0}: directory cannot start with ${_U}//${_0}: ${_U}$1${_0}" + exit 1 + fi +} + +add_dir() { + num_dirs=$((num_dirs + 1)) + dirs="$dirs${num_dirs}${1}${lf}" + if [ -n "$1" ]; then + dirs_c="$dirs_c ${_B}($num_dirs)${_0} ${_U}${1}${_0}${lf}" + else + dirs_c="$dirs_c ${_B}($num_dirs)${_0} custom directory (input required)${lf}" + fi +} + +check_sudo() { + local dir="$1" + sudo= + while true; do + if [ -e "$dir" ]; then + if [ ! -d "$dir" ]; then + >&2 echo "${_R}error${_0}: not a directory: ${_U}$dir${_0}" + return 1 + fi + if [ ! -w "$dir" ]; then + if [ "$euid" = 0 ]; then + >&2 echo "${_R}error${_0}: directory not writable: ${_U}$dir${_0}" + return 1 + else + if [ -z "$quiet" ]; then + echo "${_Y}===>${_0} using ${_U}${_G}sudo${_0} for installation" + fi + sudo=sudo + fi + fi + break + fi + if [ "$dir" = / ] || [ "$dir" = . ]; then + break + fi + dir="$(dirname -- "$dir")" + done +} + +dirs= +dirs_c= +num_dirs=0 +quiet= +algos= +url= +file= +sudo= +fd= +asked= + +command -v sudo >/dev/null 2>&1 && euid="$(id -u 2>/dev/null)" || euid=0 + +while getopts ':hqd:s:a:f:u:' opt "$@"; do + case "$opt" in + h) + printf '%s\n' "$usage" + exit + ;; + q) + if [ -n "$quiet" ]; then + >&2 echo "${_R}error${_0} duplicate option: ${_B}-${opt}${_0}" + exit 1 + fi + quiet=1 + ;; + d) + if printf "%s" "$dirs" | cut -b 2- | grep -qxF -- "$OPTARG"; then + >&2 echo "${_R}error${_0}: duplicate option: ${_B}-${opt} ${OPTARG}${_0}" + exit 1 + fi + if [ "$(printf "%s" "$OPTARG" | wc -l | tr -Cd '0-9')" != 0 ]; then + >&2 echo "${_R}error${_0}: incorrect value of ${_B}-${opt}${_0}: ${_B}${OPTARG}${_0}" + exit 1 + fi + if [ "$num_dirs" = 9 ]; then + >&2 echo "${_R}error${_0}: too many options: ${_B}-${opt}${_0}" + exit 1 + fi + if [ -n "$OPTARG" ]; then + check_dir "$OPTARG" + fi + add_dir "$OPTARG" + ;; + s) + if [ -n "$fd" ]; then + >&2 echo "${_R}error${_0} duplicate option: ${_B}-${opt}${_0}" + exit 1 + fi + if ! printf '%s' "$OPTARG" | tr '\n' x | grep -qxE '[1-9][0-9]*'; then + >&2 echo "${_R}error${_0}: incorrect value of ${_B}-${opt}${_0}: ${_B}${OPTARG}${_0}" + exit 1 + fi + fd="$OPTARG" + ;; + a) + case "$OPTARG" in + sha256|md5) + if [ -n "$algos" -a -z "${algos##*<$OPTARG>*}" ]; then + >&2 echo "${_R}error${_0}: duplicate option: ${_B}-${opt} ${OPTARG}${_0}" + exit 1 + fi + algos="$algos<$OPTARG>" + ;; + *) + >&2 echo "${_R}error${_0}: incorrect value of ${_B}-${opt}${_0}: ${_B}${OPTARG}${_0}" + exit 1 + ;; + esac + ;; + f) + if [ -n "$file" ]; then + >&2 echo "${_R}error${_0}: duplicate option: ${_B}-${opt}${_0}" + exit 1 + fi + if [ -z "$OPTARG" ]; then + >&2 echo "${_R}error${_0}: incorrect value of ${_B}-${opt}${_0}: ${_B}${OPTARG}${_0}" + exit 1 + fi + file="$(absfile "$OPTARG")" + ;; + u) + if [ -n "$url" ]; then + >&2 echo "${_R}error${_0}: duplicate option: ${_B}-${opt}${_0}" + exit 1 + fi + if [ -z "$OPTARG" ]; then + >&2 echo "${_R}error${_0}: incorrect value of ${_B}-${opt}${_0}: ${_B}${OPTARG}${_0}" + exit 1 + fi + url="$OPTARG" + ;; + \?) >&2 echo "${_R}error${_0}: invalid option: ${_B}-${OPTARG}${_0}" ; exit 1;; + :) >&2 echo "${_R}error${_0}: missing required argument: ${_B}-${OPTARG}${_0}"; exit 1;; + *) >&2 echo "${_R}internal error${_0}: unhandled option: ${_B}-${opt}${_0}" ; exit 1;; + esac +done + +if [ "$OPTIND" -le $# ]; then + >&2 echo "${_R}error${_0}: unexpected positional argument" + return 1 +fi + +if [ -n "$algos" ]; then + if [ -n "$file" ]; then + >&2 echo "${_R}error${_0}: incompatible options: ${_B}-f${_0} and ${_B}-a${_0}" + exit 1 + fi + if [ -n "$url" ]; then + >&2 echo "${_R}error${_0}: incompatible options: ${_B}-u${_0} and ${_B}-a${_0}" + exit 1 + fi +fi + +if [ "$num_dirs" = 0 ]; then + if [ "$(uname -s 2>/dev/null)" = Linux ] && + [ "$(uname -o 2>/dev/null)" = Android ] && + [ -d "${PREFIX:-/data/data/com.termux/files/usr}" ]; then + usr_local="${PREFIX:-/data/data/com.termux/files/usr}"/local + usr_local_d="${_U}\$PREFIX/local${_0}" + else + usr_local=/usr/local + usr_local_d="${_U}/usr/local${_0} " + fi + + add_dir "$usr_local" + add_dir ~/.local + add_dir "" + + dirs_c=" ${_B}(1)${_0} $usr_local_d ${_Y}<=${_0}" + if check_sudo "$usr_local" >/dev/null 2>/dev/null; then + if [ -n "$sudo" ]; then + dirs_c="${dirs_c} uses ${_U}${_G}sudo${_0}" + else + dirs_c="${dirs_c} does not need ${_U}${_G}sudo${_0}" + fi + if [ -d "$usr_local" ]; then + dirs_c="${dirs_c} (${_B}recommended${_0})" + fi + else + dirs_c="${dirs_c} ${_R}not writable${_0}" + fi + + dirs_c="${dirs_c}${lf} ${_B}(2)${_0} ${_U}~/.local${_0} ${_Y}<=${_0}" + if check_sudo ~/.local >/dev/null 2>/dev/null; then + if [ -n "$sudo" ]; then + dirs_c="${dirs_c} uses ${_U}${_G}sudo${_0}" + else + dirs_c="${dirs_c} does not need ${_U}${_G}sudo${_0}" + fi + else + dirs_c="${dirs_c} ${_R}not writable${_0}" + fi + + dirs_c="${dirs_c}${lf} ${_B}(3)${_0} custom directory ${_Y}<=${_0} manual input required${lf}" +fi + +if [ "$num_dirs" = 1 ]; then + choice=1 +else + while true; do + echo "Choose installation directory for ${_G}tmux${_0}:" + echo "" + printf "%s" "$dirs_c" + echo "" + printf "Choice: " + read -r choice + if printf "%s" "$dirs" | cut -b 1 | grep -qxF -- "$choice"; then + break + fi + if [ -n "$choice" ]; then + >&2 echo "Invalid choice: ${_R}$choice${_0}. Try again." + fi + echo + done + asked=1 +fi + +dir="$(printf "%s" "$dirs" | sed "${choice}!d" | cut -b 2-)" +if [ -z "$dir" ]; then + printf "Custom directory: " + read -r dir + check_dir "$dir" + if [ -z "$quiet" ]; then + echo + fi + asked=1 +elif [ -z "$quiet" ] && [ "$num_dirs" != 1 ]; then + echo +fi + +if [ -z "$quiet" ]; then + printf "%s\n" "${_Y}===>${_0} installing ${_G}tmux${_0} to ${_U}$dir${_0}" +fi + +check_sudo "$dir" + +$sudo mkdir -p -- "$dir" +cd -- "$dir" +dir="$(pwd)" + +if [ -z "$file" -a -z "$url" ]; then + kernel="$(uname -s | tr '[A-Z]' '[a-z]')" + arch="$(uname -m | tr '[A-Z]' '[a-z]')" + + case "$kernel" in + linux-armv8l) kernel=linux-aarch64;; + msys_nt-6.*) kernel=msys_nt-10.0;; + msys_nt-10.*) kernel=msys_nt-10.0;; + mingw32_nt-6.*) kernel=msys_nt-10.0;; + mingw32_nt-10.*) kernel=msys_nt-10.0;; + mingw64_nt-6.*) kernel=msys_nt-10.0;; + mingw64_nt-10.*) kernel=msys_nt-10.0;; + cygwin_nt-6.*) kernel=cygwin_nt-10.0;; + cygwin_nt-10.*) kernel=cygwin_nt-10.0;; + esac + + filename="tmux-${kernel}-${arch}.tar.gz" + url="$url_base/$filename" + + if [ -n "${archives##*file:$filename;*}" ]; then + >&2 echo "${_R}error${_0}: there is no prebuilt binary for your architecture" + >&2 echo + >&2 echo "See ${_U}https://github.com/romkatv/tmux-bin#compiling${_0} for building one." + exit 1 + fi + + check_sig=1 +else + check_sig=0 +fi + +if [ -n "$url" ]; then + if [ -n "${TMPDIR-}" -a '(' '(' -d "${TMPDIR-}" -a -w "${TMPDIR-}" ')' -o '!' '(' -d /tmp -a -w /tmp ')' ')' ]; then + tmpdir="$TMPDIR" + else + tmpdir=/tmp + fi + file="$tmpdir"/tmux-bin.tmp.$$.tar.gz + cleanup() { rm -f -- "$file"; } + trap cleanup INT QUIT TERM EXIT ILL PIPE + + if [ -z "$quiet" ]; then + echo "${_Y}===>${_0} fetching ${_U}${url##*/}${_0}" + fi + + ( + cd -- "${file%/*}" + file="${file##*/}" + + set +e + + if command -v curl >/dev/null 2>&1; then + err="$(command curl -fsSLo "$file" -- "$url" 2>&1)" + elif command -v wget >/dev/null 2>&1; then + err="$(command wget -O "$file" -- "$url" 2>&1)" + else + >&2 echo "${_R}error${_0}: please install ${_G}curl${_0} or ${_G}wget${_0} and retry" + exit 1 + fi + + if [ $? != 0 ]; then + >&2 printf "%s\n" "$err" + >&2 echo "${_R}error${_0}: failed to download ${_U}$url${_0}" + exit 1 + fi + ) +else + cleanup() { true; } +fi + +if [ "$check_sig" = 1 ]; then + if [ -z "$quiet" ]; then + echo "${_Y}===>${_0} verifying archive integrity" + fi + + for algo in sha256 md5; do + hash=none + case "$algo" in + sha256) + { + command -v shasum >/dev/null 2>/dev/null && + hash="$(shasum -b -a 256 -- "$file" /dev/null)" && + hash="${hash%% *}" && + [ ${#hash} -eq 64 ] + } || { + command -v sha256sum >/dev/null 2>/dev/null && + hash="$(sha256sum -b -- "$file" /dev/null)" && + hash="${hash%% *}" && + [ ${#hash} -eq 64 ] + } || { + # Note: sha256 can be from hashalot. It's incompatible. + # Thankfully, it produces shorter output. + command -v sha256 >/dev/null 2>/dev/null && + hash="$(sha256 -- "$file" /dev/null)" && + hash="${hash##* }" && + [ ${#hash} -eq 64 ] + } || { + hash=none + } + ;; + md5) + { + command -v md5sum >/dev/null 2>/dev/null && + hash="$(md5sum -b -- "$file" /dev/null)" && + hash="${hash%% *}" && + [ ${#hash} -eq 32 ] + } || { + command -v md5 >/dev/null 2>/dev/null && + hash="$(md5 -- "$file" /dev/null)" && + hash="${hash##* }" && + [ ${#hash} -eq 32 ] + } || { + hash=none + } + ;; + *) + >&2 echo "${_R}internal error${_0}: unhandled algorithm: ${_B}$algo${_0}" + exit 1 + ;; + esac + if [ "$hash" != none ]; then + if [ -n "${archives##* $algo:$hash;*}" ]; then + >&2 echo "${_R}error${_0}: ${_B}$algo${_0} signature mismatch" + >&2 echo "" + >&2 echo "Expected:" + >&2 echo "" + >&2 echo " ${_G}$(printf "%s" "$archives" | grep -F -- "${url##*/}" | sed 's/ */ /g')${_0}" + >&2 echo "" + >&2 echo "Found:" + >&2 echo "" + >&2 echo " ${_R}$algo:$hash${_0}" + exit 1 + fi + if [ -z "$quiet" ]; then + echo "${_Y}===>${_0} ${_B}$algo${_0} signature matches" + fi + algos="${algos##*<$algo>*}" + else + if [ -z "$quiet" ]; then + echo "${_Y}===>${_0} no tools to verify ${_B}$algo${_0} signature" + fi + fi + done +fi + +if [ -n "$algos" ]; then + >&2 echo "${_R}error${_0}: no tools available to verify archive integrity" + exit 1 +fi + +if [ -z "$quiet" ]; then + echo "${_Y}===>${_0} extracting files" +fi + +$sudo tar -xzf "$file" + +cleanup + +if [ -z "$quiet" ]; then + echo "" + echo "Installed ${_G}tmux${_0} to ${_U}$dir${_0}" + echo +fi + +if [ -n "$fd" ]; then + printf "%s\n" "$dir" >&"$fd" +fi + +} diff --git a/sc/setup b/sc/setup new file mode 100755 index 0000000..202b2d6 --- /dev/null +++ b/sc/setup @@ -0,0 +1,60 @@ +#!/bin/sh + +self="$(dirname -- "$0")" +old= +new= + +while getopts ':o:n:h' opt "$@"; do + case "$opt" in + h) + command cat <<\END +Usage: setup [-o old] -n new +END + exit + ;; + o) + if [ -n "$old" ]; then + >&2 echo "[z4h/setup] error: duplicate option: -$opt" + exit 1 + fi + old="$OPTARG" + ;; + n) + if [ -n "$new" ]; then + >&2 echo "[z4h/setup] error: duplicate option: -$opt" + exit 1 + fi + new="$OPTARG" + ;; + \?) >&2 echo "[z4h/setup] error: invalid option: -$OPTARG" ; exit 1;; + :) >&2 echo "[z4h/setup] error: missing required argument: -$OPTARG"; exit 1;; + *) >&2 echo "[z4h/setup] internal error: unhandled option: -$opt" ; exit 1;; + esac +done + +if [ "$OPTIND" -le $# ]; then + >&2 echo "[z4h/setup] error: unexpected positional argument" + exit 1 +fi + +if [ -z "$new" ]; then + >&2 echo "[z4h/setup] error: missing required option: -n" + exit 1 +fi + +command find "$self" -name '*.zwc' -exec rm -f -- '{}' '+' || exit + +command mkdir -p -- "$new"/bin "$new"/fn "$new"/cache/ohmyzsh "$new"/tmp || exit +if [ ! -L "$new"/z4h.zsh ]; then + command cp -f -- "$self"/../z4h.zsh "$new"/ || exit +fi +if [ ! -e "$new"/cache/last-update-ts ]; then + echo -n >"$new"/cache/last-update-ts || exit +fi + +if [ -n "$old" -a -d "$old"/stickycache ]; then + command rm -rf -- "$new"/stickycache || exit + command cp -r -- "$old"/stickycache "$new"/stickycache || exit +else + command mkdir -p -- "$new"/stickycache || exit +fi diff --git a/sc/ssh-bootstrap b/sc/ssh-bootstrap new file mode 100755 index 0000000..75ed9d9 --- /dev/null +++ b/sc/ssh-bootstrap @@ -0,0 +1,262 @@ +#!/bin/sh + +if '[' '-n' "${ZSH_VERSION-}" ']'; then + 'emulate' 'sh' '-o' 'no_aliases' '-o' 'no_glob' +else + 'set' '-f' +fi + +_z4h_bypass='' + +for _z4h_ssh_cmd in 'tar' 'tail' 'rm' 'mkdir' 'mv' 'cp' 'ln' 'wc' 'cat' 'uname' 'tr'; do + if ! command -v "$_z4h_ssh_cmd" >'/dev/null' 2>&1; then + _z4h_bypass='1' + 'break' + fi +done + +if '[' '-z' "$_z4h_bypass" ']'; then + { + _z4h_ssh_platform="$('command' 'uname' '-sm')" && + _z4h_ssh_platform="$('printf' '%s' "$_z4h_ssh_platform" | 'command' 'tr' '[A-Z]' '[a-z]')" || + _z4h_ssh_platform='' + } 2>'/dev/null' + + case "$_z4h_ssh_platform" in + 'darwin arm64');; + 'darwin x86_64');; + 'linux aarch64');; + 'linux armv6l');; + 'linux armv7l');; + 'linux armv8l');; + 'linux x86_64');; + 'linux i686');; + *) _z4h_bypass='1';; + esac +fi + +if '[' '-n' "$_z4h_bypass" ']'; then + command -v 'rm' >'/dev/null' 2>&1 && 'command' 'rm' '-f' '--' "$0" + + export TERM=^TERM^ + + if '[' '-x' "${SHELL-}" ']'; then + case "/$SHELL" in + */'ksh93') 'exec' "$SHELL" '-l'; 'exit';; + */'dash') 'exec' "$SHELL" '-l'; 'exit';; + */'vbash') 'exec' "$SHELL" '-l'; 'exit';; + */'bash') 'exec' "$SHELL" '-l'; 'exit';; + */'tcsh') 'exec' "$SHELL" '-l'; 'exit';; + */'csh') 'exec' "$SHELL" '-l'; 'exit';; + */'zsh') 'exec' "$SHELL" '-l'; 'exit';; + esac + fi + + 'printf' '\001z4h.%s%s' ^DUMP_MARKER^ 'bypass ' + 'exit' +fi + +'set' '--' "$0" + +_z4h_ssh_error() { + >&2 'printf' '\n' + >&2 'printf' '\033[33mz4h\033[0m: failed to start \033[32mzsh\033[0m on \033[1m%s\033[0m\n' ^SSH_HOST^ + >&2 'printf' '\n' + >&2 'printf' 'See error messages above to identify the culprit.\n' + >&2 'printf' '\n' + >&2 'printf' 'Open login shell on \033[1m%s\033[0m:\n' ^SSH_HOST^ + >&2 'printf' '\n' + >&2 'printf' ' \033[4;32mcommand\033[0m \033[32mssh\033[0m %s\n' ^SSH_ARGS^ + >&2 'printf' '\n' + >&2 'printf' 'Open interactive \033[32msh\033[0m on \033[1m%s\033[0m:\n' ^SSH_HOST^ + >&2 'printf' '\n' + >&2 'printf' ' \033[4;32mcommand\033[0m \033[32mssh\033[0m %s sh -i\n' ^SSH_ARGS^ + >&2 'printf' '\n' + >&2 'printf' 'Configure \033[32mz4h\033[0m \033[1mssh\033[0m to open login shell on \033[1m%s\033[0m (like above):\n' ^SSH_HOST^ + >&2 'printf' '\n' + >&2 'printf' ' \033[32mzstyle\033[0m \033[33m\047:z4h:ssh:%s\047\033[0m enable no\n' ^SSH_HOST^ + >&2 'printf' '\n' +} + +_z4h_ssh_mktemp() { + if '[' '-n' "${TMPDIR-}" '-a' '(' '(' '-d' "${TMPDIR-}" '-a' '-w' "${TMPDIR-}" ')' '-o' \ + '!' '(' '-d' '/tmp' '-a' '-w' '/tmp' ')' ')' ]; then + 'set' '--' "$TMPDIR" + else + 'set' '--' '/tmp' + fi + if command -v 'mktemp' >'/dev/null' 2>&1; then + _z4h_ssh_tmp="$('command' 'mktemp' '-d' -- "$1"/z4h-ssh.XXXXXXXXXX)" + else + _z4h_ssh_tmp="$1"/z4h-ssh.tmp."$$" + '[' '!' '-e' "$_z4h_ssh_tmp" ']' || 'command' 'rm' '-rf' '--' "$_z4h_ssh_tmp" || 'exit' + 'command' 'mkdir' '-p' '--' "$_z4h_ssh_tmp" || 'exit' + fi +} + +_z4h_ssh_cleanup='"trap" "-" "HUP" "INT" "TERM" "EXIT"; "command" "rm" "-rf" "--" "$@" 2>"/dev/null"' +'trap' "$_z4h_ssh_cleanup; 'exit' '129'" 'HUP' +'trap' "$_z4h_ssh_cleanup; 'exit' '130'" 'INT' +'trap' "$_z4h_ssh_cleanup; 'exit' '143'" 'TERM' +'trap' "$_z4h_ssh_cleanup; _z4h_ssh_error" 'EXIT' + +z4h_min_version=^MIN_VERSION^ +z4h_ssh_client=^SSH_CLIENT^ +z4h_ssh_host=^SSH_HOST^ + +'export' Z4H_SSH="$z4h_min_version":"$z4h_ssh_client":"$z4h_ssh_host" +'export' P9K_TTY="old" +if '[' ^CAN_SAVE_RESTORE_SCREEN^ '=' '1' ']'; then + 'export' _Z4H_SSH_MARKER=^DUMP_MARKER^ +fi + +^PRELUDE^ + +_z4h_ssh_tar_c_opt= +_z4h_ssh_tar_x_opt= +if _z4h_ssh_tar_v="$('command' 'tar' '--version' 2>&1)"; then + case "$_z4h_ssh_tar_v" in + *'GNU tar'*) + _z4h_ssh_tar_c_opt='--owner=0 --group=0' + _z4h_ssh_tar_x_opt='--warning=no-unknown-keyword --warning=no-timestamp --no-same-owner' + ;; + esac +fi + +_z4h_ssh_mktemp +'set' '--' "$@" "$_z4h_ssh_tmp" + +'command' 'tail' '-c' '+'^DUMP_POS^ '--' "$0" \ + | 'command' 'tar' '-C' "$_z4h_ssh_tmp" $_z4h_ssh_tar_x_opt '-xzf' '-' || 'exit' + +'command' 'rm' '-f' '--' "$0" || 'exit' +'shift' + +_z4h_ssh_src='0' + +for _z4h_ssh_dst in ^SEND_TO^; do + _z4h_ssh_src="$((_z4h_ssh_src + 1))" + case "$_z4h_ssh_dst" in + '') + >&2 'printf' '\033[33mz4h\033[0m: \033[31mz4h_ssh_send_files[%s]\033[0m is empty after expansion\n' "$((2 * _z4h_ssh_src + 2))" + 'exit' '1' + ;; + */) + >&2 'printf' '\033[33mz4h\033[0m: \033[1mz4h_ssh_send_files[%s]\033[0m ends with \033[1m/\033[0m after expansion: \033[31m%s\033[0m\n' "$((2 * _z4h_ssh_src + 2))" "$_z4h_ssh_dst" + 'exit' '1' + ;; + /*);; + *) + >&2 'printf' '\033[33mz4h\033[0m: \033[1mz4h_ssh_send_files[%s]\033[0m is not absolute: \033[31m%s\033[0m\n' "$((2 * _z4h_ssh_src + 2))" "$_z4h_ssh_dst" + 'exit' '1' + ;; + esac + if '[' '-e' "$_z4h_ssh_dst" ']'; then + 'command' 'rm' '-rf' '--' "$_z4h_ssh_dst" || 'exit' + fi + if '[' '-e' "$1"/"$_z4h_ssh_src" ']'; then + _z4h_ssh_dir="${_z4h_ssh_dst%/*}" + if '[' '!' '-e' "$_z4h_ssh_dir" ]; then + 'command' 'mkdir' '-p' '--' "$_z4h_ssh_dir" || 'exit' + fi + if ! 'command' 'mv' '-f' '--' "$1"/"$_z4h_ssh_src" "$_z4h_ssh_dst" 2>'/dev/null'; then + 'command' 'cp' '-rf' '--' "$1"/"$_z4h_ssh_src" "$_z4h_ssh_dst" || 'exit' + fi + fi +done + +'rm' '-rf' '--' "$_z4h_ssh_tmp" || 'exit' + +^SETUP^ + +'set' '--' "$0" +'printf' '' >"$0" || 'exit' + +( + 'trap' '-' 'HUP' 'INT' 'TERM' 'EXIT' + 'unset' '-f' '_z4h_ssh_error' '_z4h_ssh_mktemp' + 'unset' '_z4h_ssh_cleanup' '_z4h_ssh_tar_v' '_z4h_ssh_tar_c_opt' '_z4h_ssh_tar_x_opt' + 'unset' '_z4h_ssh_cmd' '_z4h_ssh_tmp' '_z4h_ssh_src' '_z4h_ssh_dst' 'z4h_min_version' + 'unset' 'z4h_ssh_client' 'z4h_ssh_host' '_z4h_ssh_platform' + export _z4h_ssh_feedback="$0" + Z4H_BOOTSTRAPPING='1' + 'set' '+f' + 'set' '--' + if '[' '-n' "${ZSH_VERSION-}" ']'; then + 'setopt' 'aliases' + fi + ^RUN^ + _z4h_ssh_ret="$?" + 'set' '-f' + 'command' 'rm' '--' "$0" || 'exit' + 'exit' "$_z4h_ssh_ret" +) + +_z4h_ssh_ret="${?#0}" +'[' '-e' "$0" ']' || 'exit' "${_z4h_ssh_ret:-1}" + +'trap' "$_z4h_ssh_cleanup" 'EXIT' +'.' "$0" +'rm' '-f' '--' "$0" || 'exit' +'set' '--' + +^TEARDOWN^ + +if ^EMPTY_RETRIEVE_FROM^; then + 'trap' '-' 'HUP' 'INT' 'TERM' 'EXIT' + 'exit' "${_z4h_ssh_ret:-0}" +fi + +_z4h_ssh_mktemp +'set' '--' "$@" "$_z4h_ssh_tmp" + +_z4h_ssh_dst='0' +_z4h_ssh_dst_list='' + +for _z4h_ssh_src in ^RETRIEVE_FROM^; do + _z4h_ssh_dst="$((_z4h_ssh_dst + 1))" + if '[' '-z' "$_z4h_ssh_src" ']'; then + >&2 'printf' '\033[33mz4h\033[0m: empty file source path \033[31mnumber '$((_z4h_ssh_dst+1))'\033[0m\n' + 'exit' '1' + fi + if '[' '-e' "$_z4h_ssh_src" ']'; then + 'command' 'ln' '-s' '--' "$_z4h_ssh_src" "$_z4h_ssh_tmp"/"$_z4h_ssh_dst" || 'exit' + _z4h_ssh_dst_list="$_z4h_ssh_dst_list $_z4h_ssh_dst" + fi +done + +_z4h_dump_size='0' + +if command -v 'base64' >'/dev/null' 2>&1 && command -v 'tr' >'/dev/null' 2>&1; then + if '[' '-n' "$_z4h_ssh_dst_list" ']'; then + case "$_z4h_ssh_tar_v" in + *'GNU tar'*) _z4h_ssh_tar_opt='--owner=0 --group=0';; + esac + 'command' 'tar' '-C' "$_z4h_ssh_tmp" $tar_opt '-czhf' "$_z4h_ssh_tmp"/dump.tar.gz \ + '--' $_z4h_ssh_dst_list || 'exit' + 'base64' <"$_z4h_ssh_tmp"/dump.tar.gz >"$_z4h_ssh_tmp"/dump.base64 || 'exit' + 'command' 'tr' '-d' '\n' <"$_z4h_ssh_tmp"/dump.base64 >"$_z4h_ssh_tmp"/dump.base64-sl || 'exit' + _z4h_dump_size="$('command' 'wc' '-c' <"$_z4h_ssh_tmp"/dump.base64-sl)" || 'exit' + fi +fi + +while '[' "${_z4h_dump_size# }" '!=' "$_z4h_dump_size" ']'; do + _z4h_dump_size="${_z4h_dump_size# }" +done + +'[' "${#_z4h_dump_size}" '-lt' '16' ']' || _z4h_dump_size='0' + +while '[' "${#_z4h_dump_size}" '-lt' '16' ']'; do + _z4h_dump_size="$_z4h_dump_size " +done + +{ + 'printf' '\001z4h.%s%s' ^DUMP_MARKER^ "$_z4h_dump_size" || 'exit' + if '[' "$_z4h_dump_size" '!=' '0 ' ']'; then + 'command' 'cat' '--' "$_z4h_ssh_tmp"/dump.base64-sl || 'exit' + fi +} 2>'/dev/null' + +'command' 'rm' '-rf' '--' "$@" || 'exit' +'trap' '-' 'HUP' 'INT' 'TERM' 'EXIT' +'exit' "${_z4h_ssh_ret:-0}" diff --git a/version b/version new file mode 100644 index 0000000..f6a2a7c --- /dev/null +++ b/version @@ -0,0 +1 @@ +500000033 diff --git a/z4h.zsh b/z4h.zsh new file mode 100644 index 0000000..0cd5ff5 --- /dev/null +++ b/z4h.zsh @@ -0,0 +1,376 @@ +'[' '-n' "${TERM-}" ']' || 'export' TERM='xterm-256color' + +'[' '-n' "${WT_SESSION-}" ']' && 'export' COLORTERM="${COLORTERM:-truecolor}" + +if '[' '-n' "${ZSH_VERSION-}" ']'; then + if '[' '-n' "${_z4h_source_called+x}" ']'; then + if '[' "${ZDOTDIR:-$HOME}" '=' "$HOME" ']'; then + >&2 'printf' '\033[33mz4h\033[0m: please use \033[4;32mexec\033[0m \033[32mzsh\033[0m instead of \033[32msource\033[0m \033[4m~/.zshenv\033[0m\n' + else + >&2 'printf' '\033[33mz4h\033[0m: please use \033[4;32mexec\033[0m \033[32mzsh\033[0m instead of \033[32msource\033[0m \033[4;33m"$ZDOTDIR"\033[0;4m/.zshenv\033[0m\n' + fi + 'return' '1' + fi + + 'typeset' '-gri' _z4h_source_called='1' + + 'emulate' 'zsh' + 'setopt' \ + 'always_to_end' 'auto_cd' 'auto_param_slash' \ + 'auto_pushd' 'c_bases' 'auto_menu' \ + 'extended_glob' 'extended_history' 'hist_expire_dups_first' \ + 'hist_find_no_dups' 'hist_ignore_dups' 'hist_ignore_space' \ + 'hist_verify' 'interactive_comments' 'multios' \ + 'no_aliases' 'no_bg_nice' 'no_bg_nice' \ + 'no_flow_control' 'no_prompt_bang' 'no_prompt_subst' \ + 'prompt_cr' 'prompt_percent' 'prompt_sp' \ + 'share_history' 'typeset_silent' 'hist_save_no_dups' \ + 'no_auto_remove_slash' 'no_list_types' 'no_beep' + + if '[' '!' '-e' "${${TMPPREFIX:-/tmp/zsh}:h}" ']'; then + if '[' '-n' "$TMPDIR" '-a' '-d' "$TMPDIR" '-a' '-w' "$TMPDIR" ']'; then + 'export' TMPPREFIX="${TMPDIR%/}/zsh" + elif '[' '-d' "/tmp" '-a' '-w' "/tmp" ']'; then + 'export' TMPPREFIX="/tmp/zsh" + fi + fi + + if '[' "$TERMINFO" '!=' ~/'.terminfo' ']' && '[' '-e' ~/".terminfo/$TERM[1]/$TERM" ']'; then + 'export' TERMINFO=~/'.terminfo' + fi + + 'set' '-A' '_z4h_script_argv' "$@" + + if '[' '-n' "${ZSH_SCRIPT+X}${ZSH_EXECUTION_STRING+X}" ']'; then + 'typeset' '-gri' _z4h_zle='0' + else + 'typeset' '-gri' _z4h_zle='1' + + PS1="%B%F{2}%n@%m%f %F{4}%~%f +%F{%(?.2.1)}%#%f%b " + RPS1="%B%F{3}z4h recovery mode%f%b" + + WORDCHARS='' + ZLE_REMOVE_SUFFIX_CHARS='' + HISTSIZE='1000000000' + SAVEHIST='1000000000' + + if '[' '-n' "$HISTFILE" ']'; then + 'typeset' '-gri' _z4h_custom_histfile='1' + else + 'typeset' '-gri' _z4h_custom_histfile='0' + HISTFILE="${ZDOTDIR:-$HOME}/.zsh_history" + fi + + if '[' '-n' "${_z4h_ssh_feedback-}" ']'; then + { 'print' '-r' '--' "HISTFILE=${(q)HISTFILE}" >"$_z4h_ssh_feedback"; } 2>'/dev/null' + 'unset' '_z4h_ssh_feedback' + fi + + if '[' '-n' "${_Z4H_LINES-}" '-a' '-n' "${_Z4H_COLUMNS-}" ']'; then + 'typeset' '-gi' LINES='_Z4H_LINES' + 'typeset' '-gi' COLUMNS='_Z4H_COLUMNS' + 'unset' '_Z4H_LINES' '_Z4H_COLUMNS' + fi + + 'bindkey' '-d' + 'bindkey' '-e' + + 'bindkey' '-s' '^[OM' '^M' + 'bindkey' '-s' '^[Ok' '+' + 'bindkey' '-s' '^[Om' '-' + 'bindkey' '-s' '^[Oj' '*' + 'bindkey' '-s' '^[Oo' '/' + 'bindkey' '-s' '^[OX' '=' + 'bindkey' '-s' '^[OH' '^[[H' + 'bindkey' '-s' '^[OF' '^[[F' + 'bindkey' '-s' '^[OA' '^[[A' + 'bindkey' '-s' '^[OB' '^[[B' + 'bindkey' '-s' '^[OD' '^[[D' + 'bindkey' '-s' '^[OC' '^[[C' + 'bindkey' '-s' '^[[1~' '^[[H' + 'bindkey' '-s' '^[[4~' '^[[F' + 'bindkey' '-s' '^[Od' '^[[1;5D' + 'bindkey' '-s' '^[Oc' '^[[1;5C' + 'bindkey' '-s' '^[^[[D' '^[[1;3D' + 'bindkey' '-s' '^[^[[C' '^[[1;3C' + 'bindkey' '-s' '^[[7~' '^[[H' + 'bindkey' '-s' '^[[8~' '^[[F' + 'bindkey' '-s' '^[[3\^' '^[[3;5~' + 'bindkey' '-s' '^[^[[3~' '^[[3;3~' + 'bindkey' '-s' '^[[1;9D' '^[[1;3D' + 'bindkey' '-s' '^[[1;9C' '^[[1;3C' + + 'bindkey' '^[[H' 'beginning-of-line' + 'bindkey' '^[[F' 'end-of-line' + 'bindkey' '^[[3~' 'delete-char' + 'bindkey' '^[[3;5~' 'kill-word' + 'bindkey' '^[[3;3~' 'kill-word' + 'bindkey' '^[k' 'backward-kill-line' + 'bindkey' '^[K' 'backward-kill-line' + 'bindkey' '^[j' 'kill-buffer' + 'bindkey' '^[J' 'kill-buffer' + 'bindkey' '^[/' 'redo' + 'bindkey' '^[[1;3D' 'backward-word' + 'bindkey' '^[[1;5D' 'backward-word' + 'bindkey' '^[[1;3C' 'forward-word' + 'bindkey' '^[[1;5C' 'forward-word' + + if '[' '-n' "${_Z4H_ORIG_CWD-}" ']'; then + 'builtin' 'cd' '-q' '--' "${(g:oce:)_Z4H_ORIG_CWD}" 2>'/dev/null' || 'builtin' 'cd' '-q' + 'builtin' 'unset' '_Z4H_ORIG_CWD' + 'builtin' 'eval' 'dirstack=()' + else + "builtin" "eval" ' + if [[ "${(%):-%1/}" == "z4h-tmux-cwd-"<->-<->-* ]]; then + [[ -r "./tmux"(#qNU) ]] && + _z4h_x="$("builtin" "source" "--" "./tmux" "list-clients" "-F" "#{client_tty} #{pane_id}" 2>"/dev/null")" && + _z4h_x="${${(@M)${(f)_z4h_x}:#${${${(%):-%1/}#z4h-tmux-cwd-<->-<->-}//.//} %<->}##* }" && + [[ "$_z4h_x" == "%"<-> ]] && + _z4h_x="$("builtin" "source" "--" "./tmux" "display-message" "-t" "$_z4h_x" "-p" "#{pane_current_path}" 2>"/dev/null")" && + [[ -n "$_z4h_x" ]] && + "builtin" "cd" "-q" "--" "$_z4h_x" 2>"/dev/null" || "builtin" "cd" "-q" + "builtin" "unset" "_z4h_x" + dirstack=() + fi' + fi + fi +fi + +if '[' '-n' "${Z4H-}" ']' && + '[' "${Z4H_URL-}" '=' 'https://raw.githubusercontent.com/romkatv/zsh4humans/v5' ']' && + '[' '-z' "${Z4H##/*}" '-a' '-r' "$Z4H"/zsh4humans/main.zsh ']'; then + if '.' "$Z4H"/zsh4humans/main.zsh; then + 'setopt' 'aliases' + 'return' + fi + 'unset' '_z4h_bootstrap' +else + _z4h_bootstrap='1' +fi + +if '[' '-n' "${_z4h_bootstrap-}" ']'; then + 'unset' '_z4h_bootstrap' + ( + if '[' '-z' "${Z4H-}" ]; then + >&2 'printf' '\033[33mz4h\033[0m: missing required parameter: \033[31mZ4H\033[0m\n' + >&2 'printf' '\n' + if '[' "${ZDOTDIR:-$HOME}" '=' "$HOME" ']'; then + >&2 'printf' 'It must be set in \033[4m~/.zshenv\033[0m:\n' + else + >&2 'printf' 'It must be set in \033[4;33m"$ZDOTDIR"\033[0;4m/.zshenv\033[0m:\n' + fi + >&2 'printf' '\n' + >&2 'printf' ' \033[32m:\033[0m \033[33m"${Z4H:=${XDG_CACHE_HOME:-$HOME/.cache}/zsh4humans/v5}"\033[0m\n' + >&2 'printf' '\n' + >&2 'printf' 'Note: The leading colon (\033[32m:\033[0m) is necessary.\n' + 'exit' '1' + fi + + if '[' '-n' "${Z4H##/*}" ']'; then + >&2 'printf' '\033[33mz4h\033[0m: invalid \033[1mZ4H\033[0m parameter: \033[31m%s\033[0m\n' "$Z4H" + >&2 'printf' '\n' + if '[' "${ZDOTDIR:-$HOME}" '=' "$HOME" ']'; then + >&2 'printf' 'It comes from \033[4m~/.zshenv\033[0m. Correct value example:\n' + else + >&2 'printf' 'It comes from \033[4;33m"$ZDOTDIR"\033[0;4m/.zshenv\033[0m. Correct value example:\n' + fi + >&2 'printf' '\n' + >&2 'printf' ' \033[32m:\033[0m \033[33m"${Z4H:=${XDG_CACHE_HOME:-$HOME/.cache}/zsh4humans/v5}"\033[0m\n' + >&2 'printf' '\n' + >&2 'printf' 'Note: The leading colon (\033[32m:\033[0m) is necessary.\n' + 'exit' '1' + fi + + if '[' '!' '-r' "$Z4H"/z4h.zsh ']'; then + >&2 'printf' '\033[33mz4h\033[0m: confusing \033[4mz4h.zsh\033[0m location\n' + >&2 'printf' '\n' + if '[' "${ZDOTDIR:-$HOME}" '=' "$HOME" ']'; then + >&2 'printf' 'Please fix \033[4m~/.zshenv\033[0m. Correct initialization example:\n' + else + >&2 'printf' 'Please fix \033[4;33m"$ZDOTDIR"\033[0;4m/.zshenv\033[0m. Correct initialization example:\n' + fi + >&2 'printf' '\n' + >&2 'printf' ' \033[32m:\033[0m \033[33m"${Z4H:=${XDG_CACHE_HOME:-$HOME/.cache}/zsh4humans/v5}"\033[0m\n' + >&2 'printf' ' \033[32m.\033[0m \033[4;33m"$Z4H"\033[0;4m/z4h.zsh\033[0m || \033[32mreturn\033[0m\n' + >&2 'printf' '\n' + >&2 'printf' 'Note: The leading colon (\033[32m:\033[0m) and dot (\033[32m.\033[0m) are necessary.\n' + 'exit' '1' + fi + + if '[' '-z' "${Z4H_URL-}" ']'; then + >&2 'printf' '\033[33mz4h\033[0m: missing required parameter: \033[31mZ4H_URL\033[0m\n' + >&2 'printf' '\n' + if '[' "${ZDOTDIR:-$HOME}" '=' "$HOME" ']'; then + >&2 'printf' 'It must be set at the top of \033[4m~/.zshenv\033[0m:\n' + else + >&2 'printf' 'It must be set at the top of \033[4;33m"$ZDOTDIR"\033[0;4m/.zshenv\033[0m:\n' + fi + >&2 'printf' '\n' + >&2 'printf' ' Z4H_URL=\033[33m"https://raw.githubusercontent.com/romkatv/zsh4humans/v5"\033[0m\n' + 'exit' '1' + fi + + v="${Z4H_URL#https://raw.githubusercontent.com/romkatv/zsh4humans/v}" + + if '[' '-z' "$v" ']' || '[' "$v" '=' "$Z4H_URL" ']'; then + >&2 'printf' '\033[33mz4h\033[0m: invalid \033[1mZ4H_URL\033[0m: \033[31m%s\033[0m\n' "$Z4H_URL" + >&2 'printf' '\n' + if '[' "${ZDOTDIR:-$HOME}" '=' "$HOME" ']'; then + >&2 'printf' 'It comes from \033[4m~/.zshenv\033[0m. Correct value example:\n' + else + >&2 'printf' 'It comes from \033[4;33m"$ZDOTDIR"\033[0;4m/.zshenv\033[0m. Correct value example:\n' + fi + >&2 'printf' '\n' + >&2 'printf' ' Z4H_URL=\033[33m"https://raw.githubusercontent.com/romkatv/zsh4humans/v5"\033[0m\n' + 'exit' '1' + fi + + if '[' "v$v" '!=' 'v5' ']'; then + >&2 'printf' '\033[33mz4h\033[0m: unexpected major version in \033[1mZ4H_URL\033[0m\n' + >&2 'printf' '\n' + >&2 'printf' 'Expected:\n' + >&2 'printf' '\n' + >&2 'printf' ' Z4H_URL=\033[33m"%s"\033[0m\n' "https://raw.githubusercontent.com/romkatv/zsh4humans/v5" + >&2 'printf' '\n' + >&2 'printf' 'Found:\n' + >&2 'printf' '\n' + >&2 'printf' ' Z4H_URL=\033[33m"%s"\033[0m\n' "$Z4H_URL" + >&2 'printf' '\n' + >&2 'printf' 'Delete \033[4m%s\033[0m to switch to \033[1mv%s\033[0m.\n' "$Z4H" "$v" + 'exit' '1' + fi + + if '[' '-e' "$Z4H"/.updating ']'; then + >&2 'printf' '\033[33mz4h\033[0m: updating \033[1m%s\033[0m\n' "zsh4humans" + else + >&2 'printf' '\033[33mz4h\033[0m: installing \033[1m%s\033[0m\n' "zsh4humans" + fi + + if '[' '-n' "${HOME-}" ']' && + '[' "$Z4H" = "$HOME"/.cache/zsh4humans/v5 ']' && + command -v 'id' >'/dev/null' 2>&1; then + euid="$('command' 'id' '-u')" || 'exit' + if '[' "$euid" '=' '0' ']'; then + home_ls="$('command' 'ls' '-ld' '--' "$HOME")" || 'exit' + home_owner="$('printf' '%s\n' "$home_ls" | 'command' 'awk' 'NR==1 {print $3}')" || 'exit' + if '[' "$home_owner" '!=' 'root' ']'; then + >&2 'printf' '\033[33mz4h\033[0m: refusing to \033[1minstall\033[0m as \033[31mroot\033[0m\n' + 'command' 'rm' '-rf' '--' "$HOME"/.cache/zsh4humans/v5 2>'/dev/null' && + 'command' 'rmdir' '--' "$HOME"/.cache/zsh4humans "$HOME"/.cache 2>'/dev/null' + 'exit' '1' + fi + fi + fi + + dir="$Z4H"/zsh4humans + + if command -v 'mktemp' >'/dev/null' 2>&1; then + tmpdir="$('command' 'mktemp' '-d' "$dir".XXXXXXXXXX)" + else + tmpdir="$dir".tmp."$$" + 'command' 'rm' '-rf' '--' "$tmpdir" || 'exit' + 'command' 'mkdir' '--' "$tmpdir" || 'exit' + fi + + ( + if '[' '-n' "${Z4H_BOOTSTRAP_COMMAND-}" ']'; then + Z4H_PACKAGE_NAME='zsh4humans' + Z4H_PACKAGE_DIR="$tmpdir"/zsh4humans-"$v" + 'eval' "$Z4H_BOOTSTRAP_COMMAND" || 'exit' + fi + + if '[' '-z' "${Z4H_BOOTSTRAP_COMMAND-}" ']'; then + url="https://github.com/romkatv/zsh4humans/archive/v$v.tar.gz" + + if command -v 'curl' >'/dev/null' 2>&1; then + err="$('command' 'curl' '-fsSL' '--' "$url" 2>&1 >"$tmpdir"/snapshot.tar.gz)" + elif command -v 'wget' >'/dev/null' 2>&1; then + err="$('command' 'wget' '-O-' '--' "$url" 2>&1 >"$tmpdir"/snapshot.tar.gz)" + else + >&2 'printf' '\033[33mz4h\033[0m: please install \033[32mcurl\033[0m or \033[32mwget\033[0m\n' + 'exit' '1' + fi + + if '[' "$?" '!=' '0' ']'; then + >&2 'printf' "%s\n" "$err" + >&2 'printf' '\033[33mz4h\033[0m: failed to download \033[31m%s\033[0m\n' "$url" + 'exit' '1' + fi + + 'command' 'tar' '-C' "$tmpdir" '-xzf' "$tmpdir"/snapshot.tar.gz || 'exit' + fi + + if '[' '-e' "$Z4H"/.updating ']'; then + if '[' '-z' "${Z4H_UPDATING-}" ']'; then + >&2 'printf' '\033[33mz4h\033[0m: \033[1mZ4H_UPDATING\033[0m does not propagate through \033[32mzsh\033[0m\n' + >&2 'printf' '\n' + >&2 'printf' 'Change \033[32mzsh\033[0m startup files to keep \033[1mZ4H_UPDATING\033[0m intact.\n' + 'exit' '1' + fi + "sh" "$tmpdir"/zsh4humans-"$v"/sc/setup '-n' "$Z4H" '-o' "$Z4H_UPDATING" || 'exit' + else + "sh" "$tmpdir"/zsh4humans-"$v"/sc/setup '-n' "$Z4H" || 'exit' + fi + 'command' 'rm' '-rf' '--' "$dir" || 'exit' + 'command' 'mv' '-f' '--' "$tmpdir"/zsh4humans-"$v" "$dir" || 'exit' + ) + + ret="$?" + 'command' 'rm' '-rf' '--' "$tmpdir" || 'exit' + 'exit' "$ret" + ) && '.' "$Z4H"/zsh4humans/main.zsh && 'setopt' 'aliases' && 'return' +fi + +'[' '-n' "${ZSH_VERSION-}" ']' && 'setopt' 'aliases' + +>&2 'printf' '\n' +>&2 'printf' '\033[33mz4h\033[0m: \033[31mcommand failed\033[0m: \033[32m.\033[0m \033[4;33m"$Z4H"\033[0m\033[4m/z4h.zsh\033[0m\n' + +'[' '-e' "$Z4H"/.updating ']' && 'return' '1' + +>&2 'printf' '\033[33mz4h\033[0m: enabling \033[1mrecovery mode\033[0m\n' +>&2 'printf' '\n' +>&2 'printf' 'See error messages above to identify the culprit.\n' +>&2 'printf' '\n' +>&2 'printf' 'Edit Zsh configuration:\n' +>&2 'printf' '\n' +if [ "${ZDOTDIR:-$HOME}" '=' "$HOME" ]; then + >&2 'printf' ' \033[32m%s\033[0m \033[4m~/.zshrc\033[0m\n' "${VISUAL:-${EDITOR:-vi}}" +else + >&2 'printf' ' \033[32m%s\033[0m \033[4;33m"$ZDOTDIR"\033[0;4m/.zshrc\033[0m\n' "${VISUAL:-${EDITOR:-vi}}" +fi +if command -v 'zsh' >'/dev/null' 2>&1; then + >&2 'printf' '\n' + >&2 'printf' 'Retry Zsh initialization:\n' + >&2 'printf' '\n' + >&2 'printf' ' \033[4;32mexec\033[0m \033[32mzsh\033[0m\n' +fi +if '[' '-n' "${ZSH_VERSION-}" ']' && command -v 'z4h' >'/dev/null' 2>&1; then + >&2 'printf' '\n' + >&2 'printf' 'If errors persist and you are desperate:\n' + >&2 'printf' '\n' + >&2 'printf' ' \033[32mz4h\033[0m \033[1mreset\033[0m\n' +fi +if '[' '-n' "$Z4H" '-a' '-z' "${Z4H##/*}" '-a' '-r' "$Z4H"/z4h.zsh ']'; then + >&2 'printf' '\n' + >&2 'printf' 'If nothing helps and you are about to give up:\n' + >&2 'printf' '\n' + >&2 'printf' ' \033[35m# nuke the entire site from orbit\033[0m\n' + >&2 'printf' ' \033[4;32msudo\033[0m \033[32mrm\033[0m -rf -- \033[4;33m"%s"\033[0m\n' "$Z4H" +fi +if command -v 'curl' >'/dev/null' 2>&1; then + >&2 'printf' '\n' + >&2 'printf' 'Give up and start over:\n' + >&2 'printf' '\n' + >&2 'printf' ' \033[32msh\033[0m -c \033[33m"\033[0m$(\033[32mcurl\033[0m -fsSL \033[4mhttps://raw.githubusercontent.com/romkatv/zsh4humans/v5/install\033[0m)\033[33m"\033[0m\n' +elif command -v 'wget' >'/dev/null' 2>&1; then + >&2 'printf' '\n' + >&2 'printf' 'Give up and start over:\n' + >&2 'printf' '\n' + >&2 'printf' ' \033[32msh\033[0m -c \033[33m"\033[0m$(\033[32mwget\033[0m -O- \033[4mhttps://raw.githubusercontent.com/romkatv/zsh4humans/v5/install\033[0m)\033[33m"\033[0m\n' +fi + +>&2 'printf' '\n' + +'return' '1' diff --git a/zb/zsh b/zb/zsh new file mode 100755 index 0000000..7379ce5 --- /dev/null +++ b/zb/zsh @@ -0,0 +1,10 @@ +#!/bin/sh + +if [ -x "${_Z4H_EXE}" ]; then + exec "${_Z4H_EXE}" "$@" +else + dir="$(dirname -- "$0")" || exit + dir="$( [ -d "$dir" ] && cd -- "$dir" && pwd )" || exit + p="$(printf '%s' "$PATH" | awk -v RS=: -v ORS=: -v dir="$dir" '$0 != dir')" || exit + PATH="${p%:}" exec zsh "$@" +fi