aboutsummaryrefslogtreecommitdiff
path: root/common/config/zsh
diff options
context:
space:
mode:
Diffstat (limited to 'common/config/zsh')
-rw-r--r--common/config/zsh/.zshenv346
-rw-r--r--common/config/zsh/.zshrc73
-rw-r--r--common/config/zsh/user/aliases.zsh208
-rw-r--r--common/config/zsh/user/bindings.zsh175
-rw-r--r--common/config/zsh/user/completion.zsh172
-rw-r--r--common/config/zsh/user/functions.zsh1607
-rw-r--r--common/config/zsh/user/options.zsh66
-rw-r--r--common/config/zsh/user/prompt.zsh679
-rw-r--r--common/config/zsh/user/prompt_minimal.zsh295
-rw-r--r--common/config/zsh/user/prompt_new.zsh863
-rw-r--r--common/config/zsh/user/prompt_simple.zsh227
11 files changed, 4711 insertions, 0 deletions
diff --git a/common/config/zsh/.zshenv b/common/config/zsh/.zshenv
new file mode 100644
index 0000000..cdab5b7
--- /dev/null
+++ b/common/config/zsh/.zshenv
@@ -0,0 +1,346 @@
+# Load local/system wide binaries and scripts
+export PATH=$HOME/.bin:$HOME/.local/bin:$HOME/.scripts:/usr/local/bin:/sbin:/usr/sbin:$PATH
+export PATH="/data/data/com.termux/files/usr/local/bin:$PATH"
+
+# List of directories to ignore (relative to ~/.scripts)
+EXCLUDE_DIRS=("assets" "test")
+
+# Add .scripts to path
+if [ -d "$HOME/.scripts" ]; then
+ while IFS= read -r -d '' dir; do
+ # Extract relative path
+ rel_path="${dir#$HOME/.scripts/}"
+
+ # Check if the directory is in the exclude list
+ skip=false
+ for exclude in "${EXCLUDE_DIRS[@]}"; do
+ if [[ "$rel_path" == "$exclude"* ]]; then
+ skip=true
+ break
+ fi
+ done
+
+ # Add to PATH if not excluded
+ if [ "$skip" = false ]; then
+ PATH="$dir:$PATH"
+ fi
+ done < <(find "$HOME/.scripts" -type d -print0)
+fi
+
+# Global TERM color
+export TERM=xterm-256color
+
+
+# Conditionally set default term
+available_terms=("wezterm" "kitty" "alacritty" "xterm")
+for term in "${available_terms[@]}"; do
+ if command -v "$term" &> /dev/null; then
+ export TERMINAL="$term"
+ break
+ fi
+done
+
+# Default Programs:
+export EDITOR=$(command -v nvim || echo "vim")
+export TEXEDIT="$EDITOR"
+export FCEDIT="$EDITOR"
+export VISUAL="$EDITOR"
+export GIT_EDITOR="$EDITOR"
+export COLORTERM="truecolor"
+export TERM="xterm-256color"
+export READER="zathura"
+export BROWSER="firefox"
+export OPENER="xdg-open"
+if command -v nvim &> /dev/null; then
+ export MANPAGER="sh -c 'col -b | nvim -c \"set ft=man ts=8 nomod nolist nonu noma\" -c \"autocmd VimEnter * call feedkeys(\\\"\\<CR>q\\\")\" -'"
+else
+ export MANPAGER="bat"
+fi
+export MANROFFOPT="-c"
+export PAGER="less"
+export FAQ_STYLE='github'
+export VIDEO="mpv"
+export IMAGE="phototonic"
+
+# XDG Paths:
+export XDG_CONFIG_HOME=${XDG_CONFIG_HOME:-$HOME/.config}
+export XDG_DATA_HOME=${XDG_DATA_HOME:-$HOME/.local/share}
+export XDG_CACHE_HOME=${XDG_CACHE_HOME:-$HOME/.cache}
+export INPUTRC="${XDG_CONFIG_HOME:-$HOME/.config}/inputrc"
+
+export ZDOTDIR="$XDG_CONFIG_HOME/zsh"
+export HISTFILE="$ZDOTDIR/.zhistory" # History filepath
+export HISTSIZE=1000000 # Maximum events for internal history
+export SAVEHIST=1000000 # Maximum events in history file
+export BANG_HIST # Treat the '!' character specially during expansion.
+export EXTENDED_HISTORY # Write the history file in the ":start:elapsed;command" format.
+export INC_APPEND_HISTORY # Write to the history file immediately, not when the shell exits.
+export SHARE_HISTORY # Share history between all sessions.
+export HIST_EXPIRE_DUPS_FIRST # Expire duplicate entries first when trimming history.
+export HIST_IGNORE_DUPS # Don't record an entry that was just recorded again.
+export HIST_IGNORE_ALL_DUPS # Delete old recorded entry if new entry is a duplicate.
+export HIST_FIND_NO_DUPS # Do not display a line previously found.
+export HIST_IGNORE_SPACE # Don't record an entry starting with a space.
+export HIST_SAVE_NO_DUPS # Don't write duplicate entries in the history file.
+export HIST_REDUCE_BLANKS # Remove superfluous blanks before recording entry.
+export HIST_VERIFY # Don't execute immediately upon history expansion.
+export HIST_BEEP # Beep when accessing nonexistent history.
+export INC_APPEND_HISTORY
+
+# Customize `ls` colours
+export LSCOLORS=ExGxBxDxCxEgEdxbxgxcxd
+
+# Other XDG paths:
+export RIPGREP_CONFIG_PATH="$XDG_CONFIG_HOME/ripgrep/ripgreprc"
+export DOCKER_CONFIG="$XDG_CONFIG_HOME/docker"
+export VSCODE_PORTABLE="$XDG_DATA_HOME/vscode"
+export GTK2_RC_FILES="$XDG_CONFIG_HOME/gtk-2.0/gtkrc"
+export PATH="/usr/bin/cmake:$PATH"
+export PATH=$PATH:/opt/google/chrome
+export DISCORD_USER_DATA_DIR="$XDG_DATA_HOME"
+export LYNX_CFG="$XDG_CONFIG_HOME/.lynxrc"
+
+# Manage Arch linux build sources
+export ASPROOT="${XDG_CACHE_HOME:-$HOME/.cache}/asp"
+
+# Homebrew
+#export PATH=/opt/homebrew/bin:$PATH
+export PATH="/opt/homebrew/sbin:$PATH"
+
+# Nix-profile
+export PATH=$HOME/.nix-profile/bin:$PATH
+
+# GnuPG
+export GPG_TTY=$(tty)
+#export GNUPGHOME="$XDG_CONFIG_HOME/gnupg"
+
+# Nvim
+export NVIM_TUI_ENABLE_TRUE_COLOR=1
+
+# Let FZF use ripgrep by default
+if type rg &> /dev/null; then
+ export FZF_DEFAULT_COMMAND="rg --files --hidden --glob '!{node_modules/*,.git/*}'"
+ export FZF_DEFAULT_OPTS='-m --height 50% --border'
+ export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND"
+fi
+
+# Zoxide (cd alternative)
+if command -v zoxide >/dev/null 2>&1; then
+ eval "$(zoxide init zsh)"
+fi
+
+export XDG_MENU_PREFIX=gnome-
+
+# enable git scripts
+export DEVELOPMENT_DIRECTORY="$HOME/code"
+
+# Android Home
+export ANDROID_HOME=/opt/android-sdk
+export PATH=$ANDROID_HOME/cmdline-tools/latest/bin:$PATH
+#export PATH=$ANDROID_HOME/cmdline-tools/bin:$PATH
+export PATH=$ANDROID_HOME/tools:$PATH
+export PATH=$ANDROID_HOME/tools/bin:$PATH
+export PATH=$ANDROID_HOME/platform-tools:$PATH
+# Android emulator PATH
+export PATH=$ANDROID_HOME/emulator:$PATH
+# Android SDK ROOT PATH
+export ANDROID_SDK_ROOT=/opt/android-sdk
+export PATH=$ANDROID_SDK_ROOT:$PATH
+#export ANDROID_SDK_HOME="${XDG_CONFIG_HOME:-$HOME/.config}/android"
+
+# Programming Environment Variables:
+
+# Rust
+export RUSTUP_HOME=${XDG_DATA_HOME:-$HOME/.local/share}/rustup
+export CARGO_HOME=${XDG_DATA_HOME:-$HOME/.local/share}/cargo
+export PATH="${CARGO_HOME}/bin:${RUSTUP_HOME}/bin:$PATH"
+#export PATH="$PATH:$CARGO_HOME/bin"
+#[[ -d $CARGO_HOME/bin ]] && path=($CARGO_HOME/bin $path)
+if which rustc > /dev/null; then export RUST_BACKTRACE=1; fi
+#export PATH="$HOME/.cargo/bin:$PATH"
+#export CARGO_HOME=${XDG_DATA_HOME}/cargo
+#export RUSTUP_HOME=${XDG_DATA_HOME}/rustup
+
+
+# Dotnet
+# # Currently dotnet does not support XDG ( https://github.com/dotnet/sdk/issues/10390 )
+#export DOTNET_TOOLS_DIR="$HOME/.dotnet/tools"
+export DOTNET_HOME=${XDG_DATA_HOME:-$HOME/.local/share}/dotnet
+export DOTNET_CLI_HOME="$XDG_CONFIG_HOME/dotnet"
+#mkdir -p "$DOTNET_CLI_HOME";
+export PATH="$PATH":"$DOTNET_HOME"/tools
+export DOTNET_ROOT=/opt/dotnet
+# Disable telemetry for dotnet apps
+export DOTNET_CLI_TELEMETRY_OPTOUT=1
+
+
+# Java
+#export JAVA_HOME=/usr/lib/jvm/default-java
+#export JAVA_HOME='/usr/lib/jvm/java-8-openjdk'
+#export JAVA_HOME='/usr/lib/jvm/java-10-openjdk'
+#export JAVA_HOME='/usr/lib/jvm/java-11-openjdk'
+#export JAVA_HOME='/usr/lib/jvm/java-17-openjdk'
+export JAVA_HOME='/usr/lib/jvm/java-20-openjdk'
+#export PATH=$JAVA_HOME/bin:$PATH
+export _JAVA_OPTIONS=-Djava.util.prefs.userRoot="$XDG_CONFIG_HOME"/java
+#export DEFAULT_JVM_OPTS='"-Dcom.android.sdklib.toolsdir=$APP_HOME" -XX:+IgnoreUnrecognizedVMOptions'
+#export _JAVA_AWT_WM_NONREPARENTING=1
+#export JAVA_OPTS='-XX:+IgnoreUnrecognizedVMOptions --add-modules java.se.ee'
+#export JAVA_OPTS='-XX:+IgnoreUnrecognizedVMOptions --add-modules java.xml.bind'
+#Windows:
+#set JAVA_OPTS=-XX:+IgnoreUnrecognizedVMOptions --add-modules java.se.ee
+
+
+# Dart/Flutter
+export PATH="/opt/flutter/bin:/usr/lib/dart/bin:$PATH"
+
+
+# Go
+export GO_PATH=${XDG_DATA_HOME}/go
+export GOPATH="${XDG_DATA_HOME:-$HOME/.local/share}/go"
+
+
+# Javascript
+# NVM
+export NVM_DIR="$HOME/.config/nvm"
+[ -s "$NVM_DIR/bash_completion" ] && \. "$NVM_DIR/bash_completion"
+#[ -s "$NVM_DIR/nvm.sh" ] && \. "$NVM_DIR/nvm.sh" # This loads nvm
+
+# global node installs (gross)
+[[ -d "$XDG_DATA_HOME/node/bin" ]] && path=($XDG_DATA_HOME/node/bin $path)
+export NODE_REPL_HISTORY="$XDG_DATA_HOME"/node_repl_history
+export NPM_CONFIG_USERCONFIG=$XDG_CONFIG_HOME/npm/npmrc
+#export NPM_CONFIG_INIT_AUTHOR_NAME='srdusr'
+#export NPM_CONFIG_INIT_AUTHOR_EMAIL='trevorgray@srdusr.com'
+#export NPM_CONFIG_INIT_AUTHOR_URL='https://srdusr.com'
+#export NPM_CONFIG_INIT_LICENSE='GPL-3.0'
+#export NPM_CONFIG_INIT_VERSION='0.0.0'
+#export NPM_CONFIG_SIGN_GIT_TAG='true'
+
+export BUN_INSTALL="$HOME/.bun"
+export PATH="$BUN_INSTALL/bin:$PATH"
+
+# Register Bun completion
+#fpath=("$HOME/.bun" $fpath)
+
+# Yarn
+#if command -v yarn >/dev/null 2>&1; then
+# export PATH="$PATH:`yarn global bin`"
+#fi
+#export PATH="$(yarn global bin):$PATH"
+#YARN_PATH="$HOME/.yarn/bin"
+#YARN_BIN_EXPORT="$HOME/.config/yarn/global/node_modules/.bin"
+
+# Ruby
+export GEM_PATH="$XDG_DATA_HOME/ruby/gems"
+export GEM_SPEC_CACHE="$XDG_DATA_HOME/ruby/specs"
+export GEM_HOME="$XDG_DATA_HOME/ruby/gems"
+#if [[ -d ~/.gem/ruby ]]; then
+# ver=$(find ~/.gem/ruby/* -maxdepth 0 | sort -rV | head -n 1)
+# export PATH="$PATH:${ver}/bin"
+#fi
+
+
+# Python
+# lazy load pyenv
+#export PYENV_ROOT=${PYENV_ROOT:-$HOME/.pyenv}
+#[[ -a $PYENV_ROOT/bin/pyenv ]] && path=($PYENV_ROOT/bin $path)
+#if type pyenv &> /dev/null || [[ -a $PYENV_ROOT/bin/pyenv ]]; then
+# function pyenv() {
+# unset pyenv
+# path=($PYENV_ROOT/shims $path)
+# eval "$(command pyenv init -)"
+# if which pyenv-virtualenv-init > /dev/null; then
+# eval "$(pyenv virtualenv-init -)"
+# export PYENV_VIRTUALENV_DISABLE_PROMPT=1
+# fi
+# pyenv $@
+# }
+#fi
+#export WORKON_HOME="$XDG_DATA_HOME/virtualenvs"
+#export WORKON_HOME=$HOME/.virtualenvs
+#export VIRTUALENVWRAPPER_PYTHON=`which python3`
+#export VIRTUALENVWRAPPER_PYTHON=$(which python3)
+#export VIRTUALENVWRAPPER_VIRTUALENV=`which virtualenv`
+#source /usr/local/bin/virtualenvwrapper.sh
+
+# Check if virtualenvwrapper.sh exists before sourcing
+if command -v virtualenvwrapper.sh >/dev/null 2>&1; then
+ export WORKON_HOME="$HOME/.virtualenvs"
+ export VIRTUALENVWRAPPER_PYTHON="$(command -v python3)"
+ export VIRTUALENVWRAPPER_VIRTUALENV="$(command -v virtualenv)"
+ source "$(command -v virtualenvwrapper.sh)"
+fi
+
+export VIRTUAL_ENV_DISABLE_PROMPT=false
+export JUPYTER_CONFIG_DIR="$XDG_CONFIG_HOME/jupyter"
+export IPYTHONDIR="$XDG_CONFIG_HOME/jupyter"
+
+# Python
+[[ "$(uname)" == "Darwin" ]] && export PYTHON_CONFIGURE_OPTS="--enable-framework"
+[[ "$(uname)" == "Linux" ]] && export PYTHON_CONFIGURE_OPTS="--enable-shared"
+
+export PYENV_ROOT="$HOME/.pyenv"
+export PATH="$PYENV_ROOT/bin:$PATH"
+
+# PHP
+PATH="$HOME/.config/composer/vendor/bin:$PATH"
+
+
+# Lua
+export PATH=$PATH:/usr/local/luarocks/bin
+#export PATH="$XDG_DATA_HOME/luarocks/bin:$PATH"
+
+#ver=$(find lua* -maxdepth 0 | sort -rV | head -n 1)
+#export LUA_PATH="$LUA_PATH:${ver}/share/lua/5.1/?.lua;${ver}/share/lua/5.1/?/init.lua;;"
+#export LUA_CPATH="$LUA_CPATH:${ver}/lib/lua/5.1/?.so;;"
+
+#LUAROCKS_PREFIX=/usr/local
+#export LUA_PATH="$LUAROCKS_PREFIX/share/lua/5.1/?.lua;$LUAROCKS_PREFIX/share/lua/5.1/?/init.lua;;"
+#export LUA_CPATH="$LUAROCKS_PREFIX/lib/lua/5.1/?.so;;"
+
+#export LUA_PATH="<path-to-add>;;"
+#export LUA_CPATH="./?.so;/usr/local/lib/lua/5.3/?.so;
+# /usr/local/share/lua/5.3/?.so;<path-to-add>"
+
+
+# Program settings
+#export MOZ_USE_XINPUT2="1" # Mozilla smooth scrolling/touchpads.
+# Pixel-perfect Firefox touchpad scrolling
+export MOZ_USE_XINPUT2=1
+
+# Cmake
+export PKG_CONFIG_PATH="/usr/local/lib64/pkgconfig:$PKG_CONFIG_PATH"
+
+
+# Scaling
+#export QT_AUTO_SCREEN_SCALE_FACTOR=0
+#export QT_SCALE_FACTOR=1
+#export QT_SCREEN_SCALE_FACTORS="1;1;1"
+#export GDK_SCALE=1
+#export GDK_DPI_SCALE=1
+
+## Prevent duplicate paths
+#typeset -U PATH path
+#
+## Default most programs to use fcitx global keyboard configurations
+#export GTK_IM_MODULE='fcitx'
+#export QT_IM_MODULE='fcitx'
+#export SDL_IM_MODULE='fcitx'
+#export XMODIFIERS='@im=fcitx'
+
+
+# Start blinking
+export LESS_TERMCAP_mb=$(tput bold; tput setaf 2) # green
+# Start bold
+export LESS_TERMCAP_md=$(tput bold; tput setaf 2) # green
+# Start stand out
+export LESS_TERMCAP_so=$(tput bold; tput setaf 3) # yellow
+# End standout
+export LESS_TERMCAP_se=$(tput rmso; tput sgr0)
+# Start underline
+export LESS_TERMCAP_us=$(tput smul; tput bold; tput setaf 1) # red
+# End Underline
+export LESS_TERMCAP_ue=$(tput sgr0)
+# End bold, blinking, standout, underline
+export LESS_TERMCAP_me=$(tput sgr0).
diff --git a/common/config/zsh/.zshrc b/common/config/zsh/.zshrc
new file mode 100644
index 0000000..0ada4f5
--- /dev/null
+++ b/common/config/zsh/.zshrc
@@ -0,0 +1,73 @@
+# ███████╗███████╗██╗ ██╗██████╗ ██████╗
+# ╚══███╔╝██╔════╝██║ ██║██╔══██╗██╔════╝
+# ███╔╝ ███████╗███████║██████╔╝██║
+# ███╔╝ ╚════██║██╔══██║██╔══██╗██║
+# ███████╗███████║██║ ██║██║ ██║╚██████╗
+# ╚══════╝╚══════╝╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝
+
+# Profile zsh time
+#zmodload zsh/zprof
+
+# If not running interactively, and not being sourced, don’t do anything
+[[ $- != *i* ]] && [[ "${BASH_SOURCE[0]:-${(%):-%N}}" == "$0" ]] && return
+
+# Terminal key bindings
+#stty intr '^q' # Free Ctrl+C for copy use Ctrl+Q instead for Interrupt
+stty lnext '^-' # Free Ctrl+V for paste use Ctrl+- instead for Literal next
+stty stop undef # Disable Ctrl+S to freeze terminal
+stty start undef # Disable Ctrl+Q nfreeze terminal
+
+# Set the current prompt file (e.g., prompt, or prompt_minimal)
+ZSH_PROMPT="${ZSH_PROMPT:-prompt}"
+#ZSH_PROMPT="${ZSH_PROMPT:-prompt_minimal}"
+#ZSH_PROMPT="${ZSH_PROMPT:-prompt_new}"
+#ZSH_PROMPT="${ZSH_PROMPT:-prompt_simple}"
+
+# Source common Zsh files (excluding any that start with 'prompt')
+ZSH_SOURCES=()
+
+for zsh_source in "$HOME"/.config/zsh/user/*.zsh; do
+ if [[ $(basename "$zsh_source") == prompt* && $(basename "$zsh_source" .zsh) != "$ZSH_PROMPT" ]]; then
+ continue
+ fi
+ ZSH_SOURCES+=("$zsh_source")
+done
+
+# Source ZSH files
+for zsh_source in "${ZSH_SOURCES[@]}"; do
+ source "$zsh_source"
+done
+
+# Faster SSH
+if [[ -n "$SSH_CLIENT" ]]; then
+ export KEYTIMEOUT=10
+else
+ export KEYTIMEOUT=15
+fi
+
+# Prevent non-login shell anomalies or toolchain misidentification in VS Code
+if [[ "${TERM_PROGRAM:-}" == "vscode" ]]; then
+ unset ARGV0
+fi
+
+########## Source Plugins, should be last ##########
+
+# Load fzf keybindings and completion if fzf is installed
+if command -v fzf >/dev/null 2>&1; then
+ FZF_BASE="/usr/local/bin/fzf/shell"
+ [[ -f "${FZF_BASE}/key-bindings.zsh" ]] && source "${FZF_BASE}/key-bindings.zsh"
+ [[ -f "${FZF_BASE}/completion.zsh" ]] && source "${FZF_BASE}/completion.zsh"
+fi
+
+# Source plugins
+for plugin in \
+ "$HOME/.config/zsh/plugins/zsh-you-should-use/you-should-use.plugin.zsh" \
+ "$HOME/.config/zsh/plugins/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh" \
+ "$HOME/.config/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.plugin.zsh" \
+ "$HOME/.config/zsh/plugins/zsh-autosuggestions/zsh-autosuggestions.zsh"
+do
+ [ -f "$plugin" ] && source "$plugin"
+done
+
+# Profile zsh time
+#zprof # At the end of .zshrc
diff --git a/common/config/zsh/user/aliases.zsh b/common/config/zsh/user/aliases.zsh
new file mode 100644
index 0000000..c205a9b
--- /dev/null
+++ b/common/config/zsh/user/aliases.zsh
@@ -0,0 +1,208 @@
+########## Aliases ##########
+
+# Define alias for nvim/vim (fallback to vim)
+if command -v nvim > /dev/null; then
+ alias vi='nvim'
+else
+ alias vi='vim'
+fi
+
+#alias vv='$(history -p !vim)'
+alias vv="vim -c 'norm! ^O'"
+
+# Confirmation #
+alias mv='mv -i'
+alias cp='cp -i'
+alias ln='ln -i'
+
+# Disable 'rm'
+#alias rm='function _rm() { echo -e "\033[0;31mrm\033[0m is disabled, use \033[0;32mtrash\033[0m or \033[0;32mdel \033[0m\033[0;33m$1\033[0m"; }; _rm'
+#alias del='/bin/rm'
+
+# Use lsd for ls if available
+if command -v lsd >/dev/null 2>&1; then
+ alias ls='lsd --color=auto --group-directories-first'
+fi
+
+# ls variants
+alias l='ls -FAh --group-directories-first'
+alias la='ls -lAFh --group-directories-first'
+alias lt='ls -lFAht --group-directories-first'
+alias lr='ls -RFAh --group-directories-first'
+
+# more ls variants
+alias ldot='ls -ld .* --group-directories-first'
+alias lS='ls -1FASsh --group-directories-first'
+alias lart='ls -1Fcart --group-directories-first'
+alias lrt='ls -1Fcrt --group-directories-first'
+
+# ls with different alphabethical sorting
+#unalias ll
+#ll() { LC_COLLATE=C ls "$@" }
+
+# suffix aliases
+alias -g CP='| xclip -selection clipboard -rmlastnl'
+alias -g LL="| less exit 2>1 /dev/null"
+alias -g CA="| cat -A"
+alias -g KE="2>&1"
+alias -g NE="2>/dev/null"
+alias -g NUL=">/dev/null 2>&1"
+
+alias grep='grep --color=auto --exclude-dir={.git,.svn,.hg}'
+alias egrep='egrep --color=auto --exclude-dir={.git,.svn,.hg}'
+alias egrep='fgrep --color=auto --exclude-dir={.git,.svn,.hg}'
+
+#alias hist="grep '$1' $HISTFILE"
+alias hist="history | grep $1"
+
+
+alias gdb='gdb -q'
+alias rust-gdb='rust-gdb -q'
+
+alias cd="cd-clear-ls"
+alias clear='newline_clear'
+
+# List upto last 10 visited directories using "d" and quickly cd into any specific one
+alias d="dirs -v | head -10"
+
+# Using just a number from "0" to "9"
+alias 0="cd +0"
+alias 1="cd +1"
+alias 2="cd +2"
+alias 3="cd +3"
+alias 4="cd +4"
+alias 5="cd +5"
+alias 6="cd +6"
+alias 7="cd +7"
+alias 8="cd +8"
+alias 9="cd +9"
+
+alias sudo='sudo ' # zsh: elligible for alias expansion/fix syntax highlight
+alias sedit='sudoedit'
+#alias se='sudoedit'
+alias se='sudo -e'
+alias :q='exit 2>1 /dev/null'
+alias disk-destroyer='$(command -v dd)'
+alias dd='echo "Warning use command: disk-destroyer"'
+alias sc="systemctl"
+alias jc="journalctl"
+alias jck="journalctl -k" # Kernel
+alias jce='sudo journalctl -b --priority 0..3' # error
+alias journalctl-error='sudo journalctl -b --priority 0..3'
+alias jcssh="sudo journalctl -u sshd"
+alias tunnel='ssh -fNTL'
+# tty aliases
+#if [[ "$TERM" == 'linux' ]]; then
+# alias tmux='/usr/bin/tmux -L linux'
+#fi
+#alias logout="loginctl kill-user $(whoami)"
+
+logout() {
+ local wm
+ wm="$(windowManagerName)"
+ if [[ -n "$wm" ]]; then
+ echo "Logging out by killing window manager: $wm"
+ pkill "$wm"
+ else
+ echo "No window manager detected!" >&2
+ fi
+}
+alias lg="logout"
+
+#alias suspend='systemctl suspend && betterlockscreen -l' # Suspend(sleep) and lock screen if using systemctl
+#alias suspend='systemctl suspend' # Suspend(sleep) and lock screen if using systemctl
+alias suspend='loginctl suspend' # Suspend(sleep) and lock screen if using systemctl
+#alias shutdown='loginctl poweroff' # Suspend(sleep) and lock screen if using systemctl
+#alias shutdown='sudo /sbin/shutdown -h'
+#alias poweroff='loginctl poweroff'
+#alias reboot='loginctl reboot'
+alias reboot='sudo reboot'
+#alias hibernate='systemctl hibernate' # Hibernate
+alias lock='DISPLAY=:0 xautolock -locknow' # Lock my workstation screen from my phone
+alias oports="sudo lsof -i -P -n | grep -i 'listen'" # List open ports
+alias keyname="xev | sed -n 's/[ ]*state.* \([^ ]*\)).*/\1/p'"
+alias wget=wget --hsts-file="$XDG_CACHE_HOME/wget-hsts" # wget does not support environment variables
+alias open="xdg-open"
+alias pp='getlast 2>&1 |&tee -a output.txt'
+#alias lg='la | grep'
+alias pg='ps aux | grep'
+alias py='python'
+alias py3='python3'
+alias activate='source ~/.local/share/venv/bin/activate'
+alias sha256='shasum -a 256'
+alias rgf='rg -F'
+alias weather='curl wttr.in/durban'
+alias diary='nvim "$HOME/documents/main/inbox/diary/$(date +'%Y-%m-%d').md"'
+alias wifi='nmcli dev wifi show-password'
+alias ddg='w3m lite.duckduckgo.com'
+alias rss='newsboat'
+alias vpn='protonvpn'
+alias yt-dl="yt-dlp -f 'bestvideo[ext=mp4]+bestaudio[ext=m4a]/mp4' --restrict-filename"
+#alias com.obsproject.Studio="obs"
+#alias obs="com.obsproject.Studio"
+#alias obs-stuido="obs"
+
+# Time aliases
+alias utc='TZ=Africa/Johannesburg date'
+alias ber='TZ=Europe/Berlin date'
+alias nyc='TZ=America/New_York date'
+alias sfo='TZ=America/Los_Angeles date'
+alias utc='TZ=Etc/UTC date'
+
+alias src='source $ZDOTDIR/.zshrc'
+alias p=proxy
+
+alias cheat='~/.scripts/cheat.sh ~/documents/notes/cheatsheets'
+alias crypto='curl -s rate.sx | head -n -2 | tail -n +10'
+#alias todo='glow "$HOME"/documents/main/notes/TODO.md'
+
+alias todo='$EDITOR "$(find "$HOME"/documents/main -type f -iname "todo.md" | head -n 1)"'
+alias android-studio='/opt/android-studio/bin/studio.sh' # android-studio
+alias nomachine='/usr/NX/bin/nxplayer' # nomachine
+alias firefox="firefox-bin"
+alias discord="vesktop-bin"
+alias fetch="fastfetch"
+alias batt='upower -i /org/freedesktop/UPower/devices/battery_BAT0 | grep -E "state|to full|percentage"'
+alias emerge-fetch='sudo tail -f /var/log/emerge-fetch.log'
+alias spotify="env LD_PRELOAD=/usr/local/lib/spotify-adblock.so spotify %U"
+
+alias proofread='firejail --private --private-tmp --net=none --seccomp --caps.drop=all zathura'
+
+# NVM
+if [ -s "$NVM_DIR/nvm.sh" ]; then
+ nvm_cmds=(nvm node npm yarn)
+ for cmd in "${nvm_cmds[@]}"; do
+ alias "$cmd"="unalias ${nvm_cmds[*]} && unset nvm_cmds && . $NVM_DIR/nvm.sh && $cmd"
+ done
+fi
+
+# Kubernetes
+if command -v kubectl > /dev/null; then
+ replaceNS() { kubectl config view --minify --flatten --context=$(kubectl config current-context) | yq ".contexts[0].context.namespace=\"$1\"" ; }
+ alias kks='KUBECONFIG=<(replaceNS "kube-system") kubectl'
+ alias kam='KUBECONFIG=<(replaceNS "authzed-monitoring") kubectl'
+ alias kas='KUBECONFIG=<(replaceNS "authzed-system") kubectl'
+ alias kar='KUBECONFIG=<(replaceNS "authzed-region") kubectl'
+ alias kt='KUBECONFIG=<(replaceNS "tenant") kubectl'
+
+ if command -v kubectl-krew > /dev/null; then
+ path=($XDG_CONFIG_HOME/krew/bin $path)
+ fi
+
+ rmfinalizers() {
+ kubectl get deployment "$1" -o json | jq '.metadata.finalizers = null' | kubectl apply -f -
+ }
+fi
+
+# Castero
+castero() {
+ if [[ -f ~/.local/share/venv/bin/activate ]]; then
+ . ~/.local/share/venv/bin/activate
+ fi
+ command castero "$@"
+}
+
+# Zoxide (cd alternative)
+if command -v zoxide >/dev/null 2>&1; then
+ eval "$(zoxide init zsh)"
+fi
diff --git a/common/config/zsh/user/bindings.zsh b/common/config/zsh/user/bindings.zsh
new file mode 100644
index 0000000..52cab06
--- /dev/null
+++ b/common/config/zsh/user/bindings.zsh
@@ -0,0 +1,175 @@
+########## Vi mode ##########
+bindkey -v
+
+local WORDCHARS='*?_-.[]~=&;!#$%^(){}<>'
+backward-kill-dir () {
+ local WORDCHARS=${WORDCHARS/\/}
+ zle backward-kill-word
+ zle -f kill
+}
+
+zle -N backward-kill-dir
+bindkey '^[^?' backward-kill-dir
+bindkey "^W" backward-kill-dir
+
+bindkey -M viins '^[[3~' delete-char
+bindkey -M vicmd '^[[3~' delete-char
+bindkey -v '^?' backward-delete-char
+bindkey -r '\e/'
+bindkey -s jk '\e'
+#bindkey "^W" backward-kill-word
+bindkey "^H" backward-delete-char # Control-h also deletes the previous char
+bindkey "^U" backward-kill-line
+bindkey "^[j" history-search-forward # or you can bind it to the down key "^[[B"
+bindkey "^[k" history-search-backward # or you can bind it to Up key "^[[A"
+
+bindkey '^[[D' backward-char # Left arrow
+bindkey '^[[C' forward-char # Right arrow
+bindkey '^[D' backward-char # Left arrow
+bindkey '^[C' forward-char # Right arrow
+bindkey '[C' forward-word
+bindkey '[D' backward-word
+bindkey -M viins '^[[D' backward-char # Left arrow
+bindkey -M viins '^[[C' forward-char # Right arrow
+
+bindkey -M vicmd '^[[D' backward-char # Left arrow
+bindkey -M vicmd '^[[C' forward-char # Right arrow
+
+# Define the 'autosuggest-execute' and 'autosuggest-accept' ZLE widgets
+autoload -Uz autosuggest-execute autosuggest-accept
+zle -N autosuggest-execute
+zle -N autosuggest-accept
+bindkey '^X' autosuggest-execute
+bindkey '^Y' autosuggest-accept
+bindkey '\M-l' accept-and-complete-next-history
+
+# Accept completion with <tab> or Ctrl+i and go to next/previous suggestions with Vi like keys: Ctrl+n/p
+zmodload -i zsh/complist
+accept-and-complete-next-history() {
+ zle expand-or-complete-prefix
+}
+zle -N accept-and-complete-next-history
+#bindkey -M menuselect '^i' accept-and-complete-next-history
+bindkey '^n' expand-or-complete
+bindkey '^p' reverse-menu-complete
+#bindkey '^I' expand-or-complete
+#bindkey '^[[Z]]' reverse-menu-complete
+bindkey -M menuselect '^[' undo
+
+# Edit line in vim with alt-e
+autoload edit-command-line; zle -N edit-command-line
+bindkey '^e' edit-command-line
+bindkey '^[e' edit-command-line # alt + e
+
+# Allow CTRL+D to exit zsh with partial command line (non empty line)
+exit_zsh() { exit }
+zle -N exit_zsh
+bindkey '^D' exit_zsh
+
+# Copy/Paste
+# Safe clipboard copy
+smart_copy() {
+ local text="${LBUFFER}${RBUFFER}"
+
+ # Prefer Wayland, fallback to X11, then others
+ if command -v wl-copy >/dev/null 2>&1 && [[ "$WAYLAND_DISPLAY" || "$XDG_SESSION_TYPE" == "wayland" ]]; then
+ echo -n "$text" | wl-copy --foreground --type text/plain 2>/dev/null || true
+ elif command -v xclip >/dev/null 2>&1 && [[ "$DISPLAY" || "$XDG_SESSION_TYPE" == "x11" || "$XDG_SESSION_TYPE" == "x11-xwayland" ]]; then
+ echo -n "$text" | xclip -selection clipboard 2>/dev/null || true
+ elif [[ "$(uname -s)" == "Darwin" ]] && command -v pbcopy >/dev/null 2>&1; then
+ echo -n "$text" | pbcopy 2>/dev/null || true
+ elif [[ "$OSTYPE" == "cygwin" || "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
+ echo -n "$text" | clip.exe 2>/dev/null || true
+ else
+ echo "smart_copy: No supported clipboard utility found." >&2
+ fi
+}
+
+# Safe clipboard paste
+smart_paste() {
+ local clip=""
+ if command -v wl-paste >/dev/null 2>&1 && [[ "$WAYLAND_DISPLAY" || "$XDG_SESSION_TYPE" == "wayland" ]]; then
+ clip=$(wl-paste --no-newline 2>/dev/null)
+ elif command -v xclip >/dev/null 2>&1 && [[ "$DISPLAY" || "$XDG_SESSION_TYPE" == "x11" || "$XDG_SESSION_TYPE" == "x11-xwayland" ]]; then
+ clip=$(xclip -selection clipboard -o 2>/dev/null)
+ elif [[ "$(uname -s)" == "Darwin" ]] && command -v pbpaste >/dev/null 2>&1; then
+ clip=$(pbpaste 2>/dev/null)
+ elif [[ "$OSTYPE" == "cygwin" || "$OSTYPE" == "msys" || "$OSTYPE" == "win32" ]]; then
+ clip=$(powershell.exe -Command 'Get-Clipboard -Raw' 2>/dev/null | tr -d '\r')
+ else
+ echo "smart_paste: No supported clipboard utility found." >&2
+ fi
+
+ LBUFFER+="$clip"
+ zle reset-prompt
+}
+
+# Register widgets
+zle -N smart_copy
+zle -N smart_paste
+
+# Bind keys (optional: choose your preferred)
+bindkey '^V' smart_paste
+bindkey -M viins '^V' smart_paste
+bindkey -M vicmd '^V' smart_paste
+bindkey -M vicmd 'p' smart_paste
+
+bindkey '^Y' smart_copy
+bindkey -M viins '^Y' smart_copy
+bindkey -M vicmd '^Y' smart_copy
+bindkey -M vicmd 'y' smart_copy
+
+# In vi mode, map Alt-H and Alt-L
+#bindkey -M viins "^[u" go_up # Alt-H to go up
+#bindkey -M viins "^[o" go_into # Alt-L to go into a directory
+
+
+# Newline and clear
+function newline_clear() {
+ printf "\n"
+ command clear
+}
+
+zle -N newline_clear
+
+no_tmux_clear() {
+ zle clear-screen
+}
+zle -N no_tmux_clear
+
+# Newline before clear
+if [[ -n "$TMUX" ]]; then
+ # Bind Ctrl-L to send newline and clear screen
+ bindkey '^L' newline_clear
+else
+ bindkey '^L' no_tmux_clear
+fi
+
+# use ctrl-z to toggle in and out of bg
+function toggle_fg_bg() {
+ if [[ $#BUFFER -eq 0 ]]; then
+ BUFFER="fg"
+ zle accept-line
+ else
+ BUFFER=""
+ zle clear-screen
+ fi
+}
+zle -N toggle_fg_bg
+bindkey '^Z' toggle_fg_bg
+
+
+
+
+## Custom key bindings to control history behavior
+#bindkey -M vicmd '^[[C' vi-forward-char # Right arrow in normal mode - just move cursor
+#bindkey -M vicmd '^[[D' vi-backward-char # Left arrow in normal mode - just move cursor
+#bindkey -M vicmd '^A' beginning-of-line # Ctrl-A - go to beginning of line
+#bindkey -M vicmd '^E' end-of-line # Ctrl-E - go to end of line
+
+# Disable automatic suggestion accept on right arrow in normal mode
+
+## Additional vi-mode key bindings to prevent unwanted history completion
+## Disable automatic history completion in normal mode
+#bindkey -M vicmd '^[[C' vi-forward-char # Right arrow - just move right, don't complete
+#bindkey -M vicmd '^[[D' vi-backward-char # Left arrow - just move left
diff --git a/common/config/zsh/user/completion.zsh b/common/config/zsh/user/completion.zsh
new file mode 100644
index 0000000..2445548
--- /dev/null
+++ b/common/config/zsh/user/completion.zsh
@@ -0,0 +1,172 @@
+#!/bin/zsh
+
+########## Completion(s) ##########
+
+autoload -Uz compinit
+_comp_path="${XDG_CACHE_HOME:-$HOME/.cache}/zcompdump"
+
+# Expands globs in conditional expressions
+if [[ $_comp_path(#qNmh-20) ]]; then
+ # -C (skip function check) implies -i (skip security check).
+ compinit -C -d "$_comp_path"
+else
+ mkdir -p "$_comp_path:h"
+ compinit -i -d "$_comp_path"
+ # Keep $_comp_path younger than cache time even if it isn't regenerated.
+ touch "$_comp_path"
+fi
+unset _comp_path
+
+# Skip the not really helpful global compinit
+skip_global_compinit=0
+
+DISABLE_MAGIC_FUNCTIONS=true
+
+
+#zstyle ':completion:*' menu select=1
+#zstyle ':completion:*:directory-stack' list-colors '=(#b) #([0-9]#)*( *)==95=38;5;12'
+
+# Options
+#setopt COMPLETE_IN_WORD # Complete from both ends of a word.
+##setopt ALWAYS_TO_END # Move cursor to the end of a completed word.
+##setopt PATH_DIRS # Perform path search even on command names with slashes.
+#setopt AUTO_MENU # Show completion menu on a successive tab press.
+#setopt AUTO_LIST # Automatically list choices on ambiguous completion.
+#setopt AUTO_PARAM_SLASH # If completed parameter is a directory, add a trailing slash.
+#setopt EXTENDED_GLOB # Needed for file modification glob modifiers with compinit.
+#unsetopt MENU_COMPLETE # Do not autoselect the first completion entry.
+
+## Disable all custom completions
+unsetopt COMPLETE_IN_WORD
+#unsetopt AUTO_MENU
+#unsetopt AUTO_LIST
+#unsetopt AUTO_PARAM_SLASH
+#unsetopt EXTENDED_GLOB
+#unsetopt MENU_COMPLETE # Do not autoselect the first completion entry.
+
+# Optional: Uncomment to disable waiting dots on completion
+# COMPLETION_WAITING_DOTS="false"
+
+setopt ALWAYS_TO_END # Move cursor to the end of a completed word.
+setopt PATH_DIRS # Perform path search even on command names with slashes.
+setopt AUTO_MENU # Show completion menu on a successive tab press.
+setopt AUTO_LIST # Automatically list choices on ambiguous completion.
+setopt AUTO_PARAM_SLASH # If completed parameter is a directory, add a trailing slash.
+setopt EXTENDED_GLOB # Needed for file modification glob modifiers with compinit.
+unsetopt MENU_COMPLETE # Do not autoselect the first completion entry.
+
+# Test the behavior with just the basics
+compinit
+# Variables
+LS_COLORS=${LS_COLORS:-'di=34:ln=35:so=32:pi=33:ex=31:bd=36;01:cd=33;01:su=31;40;07:sg=36;40;07:tw=32;40;07:ow=33;40;07:'}
+
+# Styles
+# Defaults.
+zstyle ':completion:*:default' list-colors ${(s.:.)LS_COLORS}
+zstyle ':completion:*:default' list-prompt '%S%M matches%s'
+
+# Use caching to make completion for commands such as dpkg and apt usable.
+zstyle ':completion::complete:*' use-cache on
+zstyle ':completion::complete:*' cache-path "${XDG_CACHE_HOME:-$HOME/.cache}/zcompcache"
+
+
+# Group matches and describe.
+zstyle ':completion:*:*:*:*:*' menu select
+zstyle ':completion:*:matches' group 'yes'
+zstyle ':completion:*:options' description 'yes'
+zstyle ':completion:*:options' auto-description '%d'
+zstyle ':completion:*:corrections' format ' %F{green}-- %d (errors: %e) --%f'
+zstyle ':completion:*:descriptions' format ' %F{yellow}-- %d --%f'
+zstyle ':completion:*:messages' format ' %F{purple} -- %d --%f'
+zstyle ':completion:*:warnings' format ' %F{red}-- no matches found --%f'
+zstyle ':completion:*' format ' %F{yellow}-- %d --%f'
+zstyle ':completion:*' group-name ''
+zstyle ':completion:*' verbose yes
+
+# Fuzzy match mistyped completions.
+zstyle ':completion:*' completer _complete _match _approximate
+zstyle ':completion:*:match:*' original only
+zstyle ':completion:*:approximate:*' max-errors 1 numeric
+
+# Increase the number of errors based on the length of the typed word. But make
+# sure to cap (at 7) the max-errors to avoid hanging.
+zstyle -e ':completion:*:approximate:*' max-errors 'reply=($((($#PREFIX+$#SUFFIX)/3>7?7:($#PREFIX+$#SUFFIX)/3))numeric)'
+
+# Don't complete unavailable commands.
+zstyle ':completion:*:functions' ignored-patterns '(_*|pre(cmd|exec))'
+
+# Array completion element sorting.
+zstyle ':completion:*:*:-subscript-:*' tag-order indexes parameters
+
+# Directories
+zstyle ':completion:*:*:cd:*' tag-order local-directories directory-stack path-directories
+zstyle ':completion:*:*:cd:*:directory-stack' menu yes select
+zstyle ':completion:*:-tilde-:*' group-order 'named-directories' 'path-directories' 'users' 'expand'
+zstyle ':completion:*' squeeze-slashes true
+
+# History
+zstyle ':completion:*:history-words' stop yes
+zstyle ':completion:*:history-words' remove-all-dups yes
+zstyle ':completion:*:history-words' list false
+zstyle ':completion:*:history-words' menu yes
+
+# Environment Variables
+zstyle ':completion::*:(-command-|export):*' fake-parameters ${${${_comps[(I)-value-*]#*,}%%,*}:#-*-}
+
+# Populate hostname completion. But allow ignoring custom entries from static
+# */etc/hosts* which might be uninteresting.
+zstyle -a ':completion:*:hosts' etc-host-ignores '_etc_host_ignores'
+
+zstyle -e ':completion:*:hosts' hosts 'reply=(
+ ${=${=${=${${(f)"$(cat {/etc/ssh/ssh_,~/.ssh/}known_hosts(|2)(N) 2> /dev/null)"}%%[#| ]*}//\]:[0-9]*/ }//,/ }//\[/ }
+ ${=${(f)"$(cat /etc/hosts(|)(N) <<(ypcat hosts 2> /dev/null))"}%%(\#${_etc_host_ignores:+|${(j:|:)~_etc_host_ignores}})*}
+ ${=${${${${(@M)${(f)"$(cat ~/.ssh/config 2> /dev/null)"}:#Host *}#Host }:#*\**}:#*\?*}}
+)'
+
+# Don't complete uninteresting users...
+zstyle ':completion:*:*:*:users' ignored-patterns \
+ adm amanda apache avahi beaglidx bin cacti canna clamav daemon \
+ dbus distcache dovecot fax ftp games gdm gkrellmd gopher \
+ hacluster haldaemon halt hsqldb ident junkbust ldap lp mail \
+ mailman mailnull mldonkey mysql nagios \
+ named netdump news nfsnobody nobody nscd ntp nut nx openvpn \
+ operator pcap postfix postgres privoxy pulse pvm quagga radvd \
+ rpc rpcuser rpm shutdown squid sshd sync uucp vcsa xfs '_*'
+
+# ... unless we really want to.
+zstyle '*' single-ignored show
+
+# Ignore multiple entries.
+zstyle ':completion:*:(rm|kill|diff):*' ignore-line other
+zstyle ':completion:*:rm:*' file-patterns '*:all-files'
+
+# Kill
+zstyle ':completion:*:*:*:*:processes' command 'ps -u $LOGNAME -o pid,user,command -w'
+zstyle ':completion:*:*:kill:*:processes' list-colors '=(#b) #([0-9]#) ([0-9a-z-]#)*=01;36=0=01'
+zstyle ':completion:*:*:kill:*' menu yes select
+zstyle ':completion:*:*:kill:*' force-list always
+zstyle ':completion:*:*:kill:*' insert-ids single
+
+# Man
+zstyle ':completion:*:manuals' separate-sections true
+zstyle ':completion:*:manuals.(^1*)' insert-sections true
+
+# Media Players
+zstyle ':completion:*:*:mpg123:*' file-patterns '*.(mp3|MP3):mp3\ files *(-/):directories'
+zstyle ':completion:*:*:mpg321:*' file-patterns '*.(mp3|MP3):mp3\ files *(-/):directories'
+zstyle ':completion:*:*:ogg123:*' file-patterns '*.(ogg|OGG|flac):ogg\ files *(-/):directories'
+zstyle ':completion:*:*:mocp:*' file-patterns '*.(wav|WAV|mp3|MP3|ogg|OGG|flac):ogg\ files *(-/):directories'
+
+# Mutt
+if [[ -s "$HOME/.mutt/aliases" ]]; then
+ zstyle ':completion:*:*:mutt:*' menu yes select
+ zstyle ':completion:*:mutt:*' users ${${${(f)"$(<"$HOME/.mutt/aliases")"}#alias[[:space:]]}%%[[:space:]]*}
+fi
+
+# SSH/SCP/RSYNC
+zstyle ':completion:*:(ssh|scp|rsync):*' tag-order 'hosts:-host:host hosts:-domain:domain hosts:-ipaddr:ip\ address *'
+zstyle ':completion:*:(scp|rsync):*' group-order users files all-files hosts-domain hosts-host hosts-ipaddr
+zstyle ':completion:*:ssh:*' group-order users hosts-domain hosts-host users hosts-ipaddr
+zstyle ':completion:*:(ssh|scp|rsync):*:hosts-host' ignored-patterns '*(.|:)*' loopback ip6-loopback localhost ip6-localhost broadcasthost
+zstyle ':completion:*:(ssh|scp|rsync):*:hosts-domain' ignored-patterns '<->.<->.<->.<->' '^[-[:alnum:]]##(.[-[:alnum:]]##)##' '*@*'
+zstyle ':completion:*:(ssh|scp|rsync):*:hosts-ipaddr' ignored-patterns '^(<->.<->.<->.<->|(|::)([[:xdigit:].]##:(#c,2))##(|%*))' '127.0.0.<->' '255.255.255.255' '::1' 'fe80::*'
diff --git a/common/config/zsh/user/functions.zsh b/common/config/zsh/user/functions.zsh
new file mode 100644
index 0000000..56f0ca1
--- /dev/null
+++ b/common/config/zsh/user/functions.zsh
@@ -0,0 +1,1607 @@
+# Dotfiles Management System
+if [[ -d "$HOME/.cfg" && -d "$HOME/.cfg/refs" ]]; then
+ # Core git wrapper with repository as work-tree
+ _config() {
+ git --git-dir="$HOME/.cfg" --work-tree="$HOME/.cfg" "$@"
+ }
+
+ # Detect OS
+ case "$(uname -s)" in
+ Linux) CFG_OS="linux" ;;
+ Darwin) CFG_OS="macos" ;;
+ MINGW*|MSYS*|CYGWIN*) CFG_OS="windows" ;;
+ *) CFG_OS="other" ;;
+ esac
+
+ # Map system path to repository path
+ _repo_path() {
+ local f="$1"
+
+ # If it's an absolute path that's not in HOME, handle it specially
+ if [[ "$f" == /* && "$f" != "$HOME/"* ]]; then
+ echo "$CFG_OS/${f#/}"
+ return
+ fi
+
+ # Check for paths that should go to the repository root
+ case "$f" in
+ common/*|linux/*|macos/*|windows/*|profile/*|README.md)
+ echo "$f"
+ return
+ ;;
+ "$HOME/"*)
+ f="${f#$HOME/}"
+ ;;
+ esac
+
+ # Default: put under OS-specific home
+ echo "$CFG_OS/home/$f"
+ }
+
+ _sys_path() {
+ local repo_path="$1"
+ local os_path_pattern="$CFG_OS/"
+
+ # Handle OS-specific files that are not in the home subdirectory
+ if [[ "$repo_path" == "$os_path_pattern"* && "$repo_path" != */home/* ]]; then
+ echo "/${repo_path#$os_path_pattern}"
+ return
+ fi
+
+ case "$repo_path" in
+ # Common configs → OS-specific config dirs
+ common/config/*)
+ case "$CFG_OS" in
+ linux)
+ local base="${XDG_CONFIG_HOME:-$HOME/.config}"
+ echo "$base/${repo_path#common/config/}"
+ ;;
+ macos)
+ echo "$HOME/Library/Application Support/${repo_path#common/config/}"
+ ;;
+ windows)
+ echo "$LOCALAPPDATA\\${repo_path#common/config/}"
+ ;;
+ *)
+ echo "$HOME/.config/${repo_path#common/config/}"
+ ;;
+ esac
+ ;;
+
+ # Common assets → stay in repo
+ common/assets/*)
+ echo "$HOME/.cfg/$repo_path"
+ ;;
+
+ # Other common files (dotfiles like .bashrc, .gitconfig, etc.) → $HOME
+ common/*)
+ echo "$HOME/${repo_path#common/}"
+ ;;
+
+ # OS-specific home
+ */home/*)
+ echo "$HOME/${repo_path#*/home/}"
+ ;;
+
+ # Profile configs and README → stay in repo
+ profile/*|README.md)
+ echo "$HOME/.cfg/$repo_path"
+ ;;
+
+ # Default fallback
+ *)
+ echo "$HOME/.cfg/$repo_path"
+ ;;
+
+ esac
+ }
+
+ # Prompts for sudo if needed and runs the command
+ _sudo_prompt() {
+ if [[ $EUID -eq 0 ]]; then
+ "$@"
+ else
+ if command -v sudo >/dev/null; then
+ sudo "$@"
+ elif command -v doas >/dev/null; then
+ doas "$@"
+ elif command -v pkexec >/dev/null; then
+ pkexec "$@"
+ else
+ echo "Error: No privilege escalation tool found."
+ return 1
+ fi
+ fi
+ }
+
+ # Main config command
+ config() {
+ local cmd="$1"; shift
+ local target_dir=""
+ # Parse optional --target flag for add
+ if [[ "$cmd" == "add" ]]; then
+ while [[ "$1" == --* ]]; do
+ case "$1" in
+ --target|-t)
+ target_dir="$2"
+ shift 2
+ ;;
+ *)
+ echo "Unknown option: $1"
+ return 1
+ ;;
+ esac
+ done
+ fi
+
+ case "$cmd" in
+ add)
+ local file_path
+ for file_path in "$@"; do
+ local repo_path
+ if [[ -n "$target_dir" ]]; then
+ local rel_path
+ if [[ "$file_path" == /* ]]; then
+ rel_path="$(basename "$file_path")"
+ else
+ rel_path="$file_path"
+ fi
+ repo_path="$target_dir/$rel_path"
+ else
+ repo_path="$(_repo_path "$file_path")"
+ fi
+
+ local full_repo_path="$HOME/.cfg/$repo_path"
+ mkdir -p "$(dirname "$full_repo_path")"
+ cp -a "$file_path" "$full_repo_path"
+
+ git --git-dir="$HOME/.cfg" --work-tree="$HOME/.cfg" add "$repo_path"
+
+ echo "Added: $file_path -> $repo_path"
+ done
+ ;;
+ rm)
+ local rm_opts=""
+ local file_path_list=()
+
+ for arg in "$@"; do
+ if [[ "$arg" == "-"* ]]; then
+ rm_opts+=" $arg"
+ else
+ file_path_list+=("$arg")
+ fi
+ done
+
+ for file_path in "${file_path_list[@]}"; do
+ local repo_path="$(_repo_path "$file_path")"
+
+ if [[ "$rm_opts" == *"-r"* ]]; then
+ _config rm --cached -r "$repo_path"
+ else
+ _config rm --cached "$repo_path"
+ fi
+
+ eval "rm $rm_opts \"$file_path\""
+ echo "Removed: $file_path"
+ done
+ ;;
+ sync)
+ local direction="${1:-to-repo}"; shift
+ _config ls-files | while read -r repo_file; do
+ local sys_file="$(_sys_path "$repo_file")"
+ local full_repo_path="$HOME/.cfg/$repo_file"
+ if [[ "$direction" == "to-repo" ]]; then
+ if [[ -e "$sys_file" && -n "$(diff "$full_repo_path" "$sys_file" 2>/dev/null || echo "diff")" ]]; then
+ cp -a "$sys_file" "$full_repo_path"
+ echo "Synced to repo: $sys_file"
+ fi
+ elif [[ "$direction" == "from-repo" ]]; then
+ if [[ -e "$full_repo_path" && -n "$(diff "$full_repo_path" "$sys_file" 2>/dev/null || echo "diff")" ]]; then
+ local dest_dir="$(dirname "$sys_file")"
+ if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then
+ _sudo_prompt mkdir -p "$dest_dir"
+ _sudo_prompt cp -a "$full_repo_path" "$sys_file"
+ else
+ mkdir -p "$dest_dir"
+ cp -a "$full_repo_path" "$sys_file"
+ fi
+ echo "Synced from repo: $sys_file"
+ fi
+ fi
+ done
+ ;;
+ status)
+ local auto_synced=()
+ while read -r repo_file; do
+ local sys_file="$(_sys_path "$repo_file")"
+ local full_repo_path="$HOME/.cfg/$repo_file"
+ if [[ -e "$sys_file" && -e "$full_repo_path" ]]; then
+ if ! diff -q "$full_repo_path" "$sys_file" >/dev/null 2>&1; then
+ cp -fa "$sys_file" "$full_repo_path"
+ auto_synced+=("$repo_file")
+ fi
+ fi
+ done < <(_config ls-files)
+ if [[ ${#auto_synced[@]} -gt 0 ]]; then
+ echo "=== Auto-synced Files ==="
+ for repo_file in "${auto_synced[@]}"; do
+ echo "synced: $(_sys_path "$repo_file") -> $repo_file"
+ done
+ echo
+ fi
+ _config status
+ echo
+ ;;
+ deploy)
+ _config ls-files | while read -r repo_file; do
+ local full_repo_path="$HOME/.cfg/$repo_file"
+ local sys_file="$(_sys_path "$repo_file")" # destination only
+
+ # Only continue if the source exists
+ if [[ -e "$full_repo_path" && -n "$sys_file" ]]; then
+ local dest_dir
+ dest_dir="$(dirname "$sys_file")"
+
+ # Create destination if needed
+ if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then
+ _sudo_prompt mkdir -p "$dest_dir"
+ _sudo_prompt cp -a "$full_repo_path" "$sys_file"
+ else
+ mkdir -p "$dest_dir"
+ cp -a "$full_repo_path" "$sys_file"
+ fi
+
+ echo "Deployed: $repo_file -> $sys_file"
+ fi
+ done
+ ;;
+ checkout)
+ echo "Checking out dotfiles from .cfg..."
+ _config ls-files | while read -r repo_file; do
+ local full_repo_path="$HOME/.cfg/$repo_file"
+ local sys_file="$(_sys_path "$repo_file")"
+
+ if [[ -e "$full_repo_path" && -n "$sys_file" ]]; then
+ local dest_dir
+ dest_dir="$(dirname "$sys_file")"
+
+ # Create destination if it doesn't exist
+ if [[ "$sys_file" == /* && "$sys_file" != "$HOME/"* ]]; then
+ _sudo_prompt mkdir -p "$dest_dir"
+ _sudo_prompt cp -a "$full_repo_path" "$sys_file"
+ else
+ mkdir -p "$dest_dir"
+ cp -a "$full_repo_path" "$sys_file"
+ fi
+
+ echo "Checked out: $repo_file -> $sys_file"
+ fi
+ done
+ ;;
+ backup)
+ local timestamp=$(date +%Y%m%d%H%M%S)
+ local backup_dir="$HOME/.dotfiles_backup/$timestamp"
+ echo "Backing up existing dotfiles to $backup_dir..."
+
+ _config ls-files | while read -r repo_file; do
+ local sys_file="$(_sys_path "$repo_file")"
+ if [[ -e "$sys_file" ]]; then
+ local dest_dir_full="$backup_dir/$(dirname "$repo_file")"
+ mkdir -p "$dest_dir_full"
+ cp -a "$sys_file" "$backup_dir/$repo_file"
+ fi
+ done
+ echo "Backup complete. To restore, copy files from $backup_dir to their original locations."
+ ;;
+ *)
+ _config "$cmd" "$@"
+ ;;
+ esac
+ }
+fi
+
+# Make SUDO_ASKPASS agnostic: pick the first available askpass binary.
+# You can predefine SUDO_ASKPASS env var to force a particular path.
+: "${SUDO_ASKPASS:=""}"
+
+# list of common askpass binaries (order: preferred -> fallback)
+_askpass_candidates=(
+ "$SUDO_ASKPASS" # user-specified (if absolute path)
+ "/usr/lib/ssh/x11-ssh-askpass"
+ "/usr/libexec/openssh/ssh-askpass"
+ "/usr/lib/ssh/ssh-askpass"
+ "/usr/bin/ssh-askpass"
+ "/usr/bin/ssh-askpass-gtk"
+ "/usr/bin/ssh-askpass-gnome"
+ "/usr/bin/ssh-askpass-qt"
+ "/usr/bin/ksshaskpass"
+ "/usr/bin/zenity" # use zenity --entry as wrapper (see below)
+ "/usr/bin/mate-ssh-askpass"
+ "/usr/bin/xdg-open" # last-resort GUI helper (not ideal)
+)
+
+find_askpass() {
+ for p in "${_askpass_candidates[@]}"; do
+ [ -z "$p" ] && continue
+ # if user gave a path in SUDO_ASKPASS we accept it only if it's executable
+ if [ -n "$SUDO_ASKPASS" ] && [ "$p" = "$SUDO_ASKPASS" ]; then
+ [ -x "$p" ] && { printf '%s\n' "$p"; return 0; }
+ continue
+ fi
+
+ # if candidate is an absolute path, test directly
+ if [ "${p#/}" != "$p" ]; then
+ [ -x "$p" ] && { printf '%s\n' "$p"; return 0; }
+ continue
+ fi
+
+ # otherwise try to resolve via PATH
+ if command -v "$p" >/dev/null 2>&1; then
+ # For zenity, we will use a small wrapper (see below)
+ printf '%s\n' "$(command -v "$p")"
+ return 0
+ fi
+ done
+
+ return 1
+}
+
+# If zenity is chosen, use a thin wrapper script so sudo -A can call it like an askpass binary.
+# This wrapper will be created in $XDG_RUNTIME_DIR or /tmp (non-persistent).
+create_zenity_wrapper() {
+ local wrapper
+ wrapper="${XDG_RUNTIME_DIR:-/tmp}/.sudo_askpass_zenity.sh"
+ cat >"$wrapper" <<'EOF'
+#!/bin/sh
+# simple zenity askpass wrapper for sudo
+# prints password to stdout so sudo -A works
+zenity --entry --title="Authentication" --text="Elevated privileges are required" --hide-text 2>/dev/null || exit 1
+EOF
+ chmod 700 "$wrapper"
+ printf '%s\n' "$wrapper"
+}
+
+# Set askpass
+if [ -z "$SUDO_ASKPASS" ]; then
+ candidate="$(find_askpass || true)"
+ if [ -n "$candidate" ]; then
+ if command -v zenity >/dev/null 2>&1 && [ "$(command -v zenity)" = "$candidate" ]; then
+ # create the wrapper and export it
+ wrapper="$(create_zenity_wrapper)"
+ export SUDO_ASKPASS="$wrapper"
+ else
+ export SUDO_ASKPASS="$candidate"
+ fi
+ else
+ # optional: leave unset or set to empty to avoid mistakes
+ unset SUDO_ASKPASS
+ fi
+fi
+# debug: (uncomment to print what was chosen)
+# printf 'SUDO_ASKPASS -> %s\n' "${SUDO_ASKPASS:-<none>}"
+
+
+# Git
+# No arguments: `git status`
+# With arguments: acts like `git`
+g() {
+ if [ $# -gt 0 ]; then
+ git "$@" # If arguments are provided, pass them to git
+ else
+ git status # Otherwise, show git status
+ fi
+}
+
+# Complete g like git
+compdef g=git
+
+# Git alias commands
+ga() { g add "$@"; } # ga: Add files to the staging area
+gaw() { g add -A && g diff --cached -w | g apply --cached -R; } # gaw: Add all changes to the staging area and unstage whitespace changes
+grm() { g rm "$@"; }
+gb() { g branch "$@"; } # gb: List branches
+gbl() { g branch -l "$@"; } # gbl: List local branches
+gbD() { g branch -D "$@"; } # gbD: Delete a branch
+gbu() { g branch -u "$@"; } # gbu: Set upstream branch
+ge() { g clone "$@"; }
+gc() { g commit "$@"; } # gc: Commit changes
+gcm() { g commit -m "$@"; } # gcm: Commit with a message
+gca() { g commit -a "$@"; } # gca: Commit all changes
+gcaa() { g commit -a --amend "$@"; } # gcaa: Amend the last commit
+gcam() { g commit -a -m "$@"; } # gcam: Commit all changes with a message
+gce() { g commit -e "$@"; } # gce: Commit with message and allow editing
+gcfu() { g commit --fixup "$@"; } # gcfu: Commit fixes in the context of the previous commit
+gco() { g checkout "$@"; } # gco: Checkout a branch or file
+gcob() { g checkout -b "$@"; } # gcob: Checkout a new branch
+gcoB() { g checkout -B "$@"; } # gcoB: Checkout a new branch, even if it exists
+gcp() { g cherry-pick "$@"; } # gcp: Cherry-pick a commit
+gcpc() { g cherry-pick --continue "$@"; } # gcpc: Continue cherry-picking after resolving conflicts
+gd() { g diff "$@"; } # gd: Show changes
+#gd^() { g diff HEAD^ HEAD "$@"; } # gd^: Show changes between HEAD^ and HEAD
+gds() { g diff --staged "$@"; } # gds: Show staged changes
+gl() { g lg "$@"; } # gl: Show a customized log
+glg() { g log --graph --decorate --all "$@"; } # glg: Show a customized log with graph
+gls() { # Query `glog` with regex query.
+ query="$1"
+ shift
+ glog --pickaxe-regex "-S$query" "$@"
+}
+gdc() { g diff --cached "$@"; } # gdc: Show changes between the working directory and the index
+gu() { g pull "$@"} # gu: Pull
+gp() { g push "$@"} # gp: Push
+gpom() { g push origin main "$@"; } # gpom: Push changes to origin main
+gr() { g remote "$@"; } # gr: Show remote
+gra() { g rebase --abort "$@"; } # gra: Abort a rebase
+grb() { g rebase --committer-date-is-author-date "$@"; } # grb: Rebase with the author date preserved
+grbom() { grb --onto master "$@"; } # grbom: Rebase onto master
+grbasi() { g rebase --autosquash --interactive "$@"; } # grbasi: Interactive rebase with autosquash
+grc() { g rebase --continue "$@"; } # grc: Continue a rebase
+grs() { g restore --staged "$@"; } # grs: Restore changes staged for the next commit
+grv() { g remote -v "$@"; } # grv: Show remote URLs after each name
+grh() { g reset --hard "$@"; } # grh: Reset the repository and the working directory
+grH() { g reset HEAD "$@"; } # grH: Reset the index but not the working directory
+#grH^() { g reset HEAD^ "$@"; } # grH^: Reset the index and working directory to the state of the HEAD's first parent
+gs() { g status -sb "$@"; } # gs: Show the status of the working directory and the index
+gsd() { g stash drop "$@"; } # gsd: Drop a stash
+gsl() { g stash list --date=relative "$@"; } # gsl: List all stashes
+gsp() { g stash pop "$@"; } # gsp: Apply and remove a single stash
+gss() { g stash show "$@"; } # gss: Show changes recorded in the stash as a diff
+gst() { g status "$@"; } # gst: Show the status of the working directory and the index
+gsu() { g standup "$@"; } # gsu: Customized standup command
+gforgotrecursive() { g submodule update --init --recursive --remote "$@"; } # gforgotrecursive: Update submodules recursively
+gfp() { g commit --amend --no-edit && g push --force-with-lease "$@"; } # gfp: Amending the last commit and force-pushing
+
+# Temporarily unset GIT_WORK_TREE
+function git_without_work_tree() {
+ # Only proceed if a git command is being run
+ if [ "$1" = "git" ]; then
+ shift
+ # Check if the current directory is inside a Git work tree
+ if git rev-parse --is-inside-work-tree &>/dev/null; then
+ # If inside a work tree, temporarily unset GIT_WORK_TREE
+ GIT_WORK_TREE_OLD="$GIT_WORK_TREE"
+ unset GIT_WORK_TREE
+ git "$@"
+ export GIT_WORK_TREE="$GIT_WORK_TREE_OLD"
+ else
+ # If not inside a work tree, call git command directly
+ git "$@"
+ fi
+ else
+ # If it's not a git command, just execute it normally
+ command "$@"
+ fi
+}
+
+# Set alias conditionally
+#alias git='git_without_work_tree git'
+
+# Set bare dotfiles repository git environment variables dynamically
+function set_git_env_vars() {
+ # Do nothing unless ~/.cfg exists and is a bare git repo
+ [[ -d "$HOME/.cfg" ]] || return
+ git --git-dir="$HOME/.cfg" rev-parse --is-bare-repository &>/dev/null || return
+
+ # Skip if last command was a package manager
+ if [[ "${(%)${(z)history[1]}}" =~ ^(pacman|yay|apt|dnf|brew|npm|pip|gem|go|cargo) ]]; then
+ return
+ fi
+
+ # Only set env vars if not already inside another Git repo
+ if ! git rev-parse --is-inside-work-tree &>/dev/null; then
+ export GIT_DIR="$HOME/.cfg"
+ export GIT_WORK_TREE="$(realpath ~)"
+ else
+ unset GIT_DIR
+ unset GIT_WORK_TREE
+ fi
+}
+
+# Hook and initial call
+function chpwd() { set_git_env_vars }
+set_git_env_vars
+
+# Git Subtrees
+function gsp() {
+ # Config file for subtrees
+ #
+ # Format:
+ # <prefix>;<remote address>;<remote branch>
+ # # Lines starting with '#' will be ignored
+ GIT_SUBTREE_FILE="$PWD/.gitsubtrees"
+
+ if [ ! -f "$GIT_SUBTREE_FILE" ]; then
+ echo "Nothing to do - file <$GIT_SUBTREE_FILE> does not exist."
+ return
+ fi
+
+ if ! command -v config &> /dev/null; then
+ echo "Error: 'config' command not found. Make sure it's available in your PATH."
+ return
+ fi
+
+ OLD_IFS=$IFS
+ IFS=$'\n'
+ for LINE in $(cat "$GIT_SUBTREE_FILE"); do
+
+ # Skip lines starting with '#'.
+ if [[ $LINE = \#* ]]; then
+ continue
+ fi
+
+ # Parse the current line.
+ PREFIX=$(echo "$LINE" | cut -d';' -f 1)
+ REMOTE=$(echo "$LINE" | cut -d';' -f 2)
+ BRANCH=$(echo "$LINE" | cut -d';' -f 3)
+
+ # Pull from the remote.
+ echo "Executing: git subtree pull --prefix=$PREFIX $REMOTE $BRANCH"
+ if git subtree pull --prefix="$PREFIX" "$REMOTE" "$BRANCH"; then
+ echo "Subtree pull successful for $PREFIX."
+ else
+ echo "Error: Subtree pull failed for $PREFIX."
+ fi
+ done
+
+ IFS=$OLD_IFS
+}
+
+# Print previous command into a file
+getlast () {
+ fc -nl $((HISTCMD - 1))
+}
+
+# Copy the current command to a file
+copy_command_to_file() {
+ # Only write the last command if BUFFER is not empty
+ if [[ -n "$BUFFER" ]]; then
+ echo "$BUFFER" > ~/command_log.txt # Overwrite with the latest command
+ else
+ # If the buffer is empty, remove the previous log file
+ command rm -f ~/command_log.txt # Optionally remove the log if no command is present
+ fi
+}
+
+# Display the latest command from the log in the user input
+display_latest_command() {
+ if [[ -f ~/command_log.txt ]]; then
+ # Read the last command from the log
+ local last_command
+ last_command=$(< ~/command_log.txt)
+
+ # Only display if the last command is not empty
+ if [[ -n "$last_command" ]]; then
+ BUFFER="$last_command" # Set the BUFFER to the last command
+ CURSOR=${#BUFFER} # Set the cursor to the end of the command
+ fi
+ fi
+ zle reset-prompt # Refresh the prompt
+}
+
+# Go up a directory
+go_up() {
+ copy_command_to_file # Copy the current command to a file
+ BUFFER="" # Clear the current command line
+ cd .. || return # Change directory and return if it fails
+ display_latest_command # Display the latest command in the user input
+}
+
+# Initialize a variable to store the previous directory
+previous_dir=""
+
+# Function to change directories
+go_into() {
+ copy_command_to_file # Copy the current command to a file
+
+ # Use fzf or another tool to choose the directory
+ local dir
+ dir=$( (ls -d */; echo "Go Last directory:") | fzf --height 40% --reverse --tac) # Include previous directory as an option
+
+ if [[ -n "$dir" ]]; then
+ # Check if the user selected the previous directory
+ if [[ "$dir" == Previous:* ]]; then
+ cd - || return # Change to the previous directory
+ else
+ cd "${dir%/}" || return # Change directory if a selection is made (remove trailing slash)
+ fi
+
+ # Save the current directory to previous_dir
+ previous_dir=$(pwd) # Update previous_dir to current directory after changing
+ BUFFER="" # Clear the current command line
+ display_latest_command # Display the last command if available
+ fi
+}
+
+# Register functions as ZLE widgets
+zle -N go_up
+zle -N go_into
+
+
+# XDG_GAMES_DIR:
+# Path to user-dirs config
+USER_DIRS_FILE="$HOME/.config/user-dirs.dirs"
+
+if [ -f "$USER_DIRS_FILE" ]; then
+ # Extract directory names from user-dirs config
+ _dirs=(
+ ${(f)"$(grep '^XDG_.*_DIR=' "$USER_DIRS_FILE" \
+ | cut -d= -f2 \
+ | tr -d '"' \
+ | sed "s|^\$HOME/||")"}
+ )
+
+ _lowercase_count=0
+ _total_count=0
+ for d in "${_dirs[@]}"; do
+ [ -n "$d" ] || continue
+ _total_count=$(( _total_count + 1 ))
+ case "$d" in
+ [a-z0-9]*)
+ _lowercase_count=$(( _lowercase_count + 1 ))
+ ;;
+ esac
+ done
+
+ # Require majority lowercase (≥70%)
+ if [ "$_total_count" -gt 0 ]; then
+ _percent=$(( 100 * _lowercase_count / _total_count ))
+ if [ "$_percent" -ge 70 ]; then
+ # Ensure the lowercase games directory exists
+ if [ ! -d "$HOME/games" ]; then
+ mkdir -p "$HOME/games"
+ fi
+
+ # Create symbolic link if it doesn't already exist
+ if [ ! -L "$HOME/Games" ] && [ ! -d "$HOME/Games" ]; then
+ ln -s "$HOME/games" "$HOME/Games"
+ fi
+
+ export XDG_GAMES_DIR="$HOME/games"
+ fi
+ fi
+
+ unset _dirs _lowercase_count _total_count _percent
+fi
+
+
+# Enter directory and list contents
+function cd-clear-ls() {
+ if [ -n "$1" ]; then
+ builtin cd "$@" 2>/dev/null || { echo "cd: no such file or directory: $1"; return 1; }
+ else
+ builtin cd ~ || return 1
+ fi
+
+ echo -e "\033[H\033[J" # Clear screen but keep scroll buffer
+
+ if [ "$PWD" != "$HOME" ] && git rev-parse --is-inside-work-tree &>/dev/null; then
+ ls -a
+ else
+ ls
+ fi
+}
+
+# cd using "up n" as a command up as many directories, example "up 3"
+up() {
+ # default parameter to 1 if non provided
+ declare -i d=${@:-1}
+ # ensure given parameter is non-negative. Print error and return if it is
+ (( $d < 0 )) && (>&2 echo "up: Error: negative value provided") && return 1;
+ # remove last d directories from pwd, append "/" in case result is empty
+ cd "$(pwd | sed -E 's;(/[^/]*){0,'$d'}$;;')/";
+}
+
+# cd into $XDG_CONFIG_HOME/$1 directory
+c() {
+ local root=${XDG_CONFIG_HOME:-~/.config}
+ local dname="$root/$1"
+ if [[ ! -d "$dname" ]]; then
+ return
+ fi
+ cd "$dname"
+}
+
+# Make and cd into directory and any parent directories
+mkcd () {
+ if [[ -z "$1" ]]; then
+ echo "Usage: mkcd <dir>" 1>&2
+ return 1
+ fi
+ mkdir -p "$1"
+ cd "$1"
+}
+
+bak() {
+ if [[ -e "$1" ]]; then
+ echo "Found: $1"
+ mv "${1%.*}"{,.bak}
+ elif [[ -e "$1.bak" ]]; then
+ echo "Found: $1.bak"
+ mv "$1"{.bak,}
+ fi
+}
+
+back() {
+ for file in "$@"; do
+ cp -r "$file" "$file".bak
+ done
+}
+
+# tre is a shorthand for tree
+tre() {
+ tree -aC -I \
+ '.git|.hg|.svn|.tmux|.backup|.vim-backup|.swap|.vim-swap|.undo|.vim-undo|*.bak|tags' \
+ --dirsfirst "$@" \
+ | less
+}
+
+# switch from/to project/package dir
+pkg() {
+ if [ "$#" -eq 2 ]; then
+ ln -s "$(readlink -f $1)" "$(readlink -f $2)"/._pkg
+ ln -s "$(readlink -f $2)" "$(readlink -f $1)"/._pkg
+ else
+ cd "$(readlink -f ./._pkg)"
+ fi
+}
+
+# Prepare C/C++ project for Language Server Protoco
+lsp-prep() {
+ (cd build && cmake .. -DCMAKE_EXPORT_COMPILE_COMMANDS=ON) \
+ && ln -sf build/compile_commands.json
+}
+
+reposize() {
+ url=`echo $1 \
+ | perl -pe 's#(?:https?://github.com/)([\w\d.-]+\/[\w\d.-]+).*#\1#g' \
+ | perl -pe 's#git\@github.com:([\w\d.-]+\/[\w\d.-]+)\.git#\1#g'
+ `
+ printf "https://github.com/$url => "
+ curl -s https://api.github.com/repos/$url \
+ | jq '.size' \
+ | numfmt --to=iec --from-unit=1024
+}
+
+# Launch a program in a terminal without getting any output,
+# and detach the process from terminal
+# (can then close the terminal without terminating process)
+-echo() {
+ "$@" &> /dev/null & disown
+}
+
+# Reload shell
+function reload() {
+ local compdump_files="$ZDOTDIR/.zcompdump*"
+
+ if ls $compdump_files &> /dev/null; then
+ rm -f $compdump_files
+ fi
+
+ exec $SHELL -l
+}
+
+#pom() {
+# local -r HOURS=${1:?}
+# local -r MINUTES=${2:-0}
+# local -r POMODORO_DURATION=${3:-25}
+#
+# bc <<< "(($HOURS * 60) + $MINUTES) / $POMODORO_DURATION"
+#}
+
+#mnt() {
+# local FILE="/mnt/external"
+# if [ ! -z $2 ]; then
+# FILE=$2
+# fi
+#
+# if [ ! -z $1 ]; then
+# sudo mount "$1" "$FILE" -o rw
+# echo "Device in read/write mounted in $FILE"
+# fi
+#
+# if [ $# = 0 ]; then
+# echo "You need to provide the device (/dev/sd*) - use lsblk"
+# fi
+#}
+#
+#umnt() {
+# local DIRECTORY="/mnt"
+# if [ ! -z $1 ]; then
+# DIRECTORY=$1
+# fi
+# MOUNTED=$(grep $DIRECTORY /proc/mounts | cut -f2 -d" " | sort -r)
+# cd "/mnt"
+# sudo umount $MOUNTED
+# echo "$MOUNTED unmounted"
+#}
+
+mntmtp() {
+ local DIRECTORY="$HOME/mnt"
+ if [ ! -z $2 ]; then
+ local DIRECTORY=$2
+ fi
+ if [ ! -d $DIRECTORY ]; then
+ mkdir $DIRECTORY
+ fi
+
+ if [ ! -z $1 ]; then
+ simple-mtpfs --device "$1" "$DIRECTORY"
+ echo "MTPFS device in read/write mounted in $DIRECTORY"
+ fi
+
+ if [ $# = 0 ]; then
+ echo "You need to provide the device number - use simple-mtpfs -l"
+ fi
+}
+
+umntmtp() {
+ local DIRECTORY="$HOME/mnt"
+ if ; then
+ DIRECTORY=$1
+ fi
+ cd $HOME
+ umount $DIRECTORY
+ echo "$DIRECTORY with mtp filesystem unmounted"
+}
+duckduckgo() {
+ lynx -vikeys -accept_all_cookies "https://lite.duckduckgo.com/lite/?q=$@"
+}
+
+wikipedia() {
+ lynx -vikeys -accept_all_cookies "https://en.wikipedia.org/wiki?search=$@"
+}
+
+#function filesize() {
+# # Check if 'du' supports the -b option, which provides sizes in bytes.
+# if du -b /dev/null > /dev/null 2>&1; then
+# local arg=-sbh; # If supported, use -sbh options for 'du'.
+# else
+# local arg=-sh; # If not supported, use -sh options for 'du'.
+# fi
+#
+# # Check if no arguments are provided.
+# if [ "$#" -eq 0 ]; then
+# # Calculate and display sizes for all files and directories in cwd.
+# du $arg ./*
+# else
+# # Calculate and display sizes for the specified files and directories.
+# du $arg -- "$@"
+# fi
+#}
+#
+
+fgl() {
+ git log --graph --color=always \
+ --format="%C(auto)%h%d %s %C(black)%C(bold)%cr" "$@" |
+ fzf --ansi --no-sort --reverse --tiebreak=index --bind=ctrl-s:toggle-sort \
+ --bind "ctrl-m:execute:
+ (grep -o '[a-f0-9]\{7\}' | head -1 |
+ xargs -I % sh -c 'git show --color=always % | less -R') << 'FZF-EOF'
+ {}
+FZF-EOF"
+}
+
+fgb() {
+ local branches branch
+ branches=$(git --no-pager branch -vv) &&
+ branch=$(echo "$branches" | fzf +m) &&
+ git checkout $(echo "$branch" | awk '{print $1}' | sed "s/.* //")
+}
+
+# +--------+
+# | Pacman |
+# +--------+
+
+# TODO can improve that with a bind to switch to what was installed
+fpac() {
+ pacman -Slq | fzf --multi --reverse --preview 'pacman -Si {1}' | xargs -ro sudo pacman -S
+}
+
+fyay() {
+ yay -Slq | fzf --multi --reverse --preview 'yay -Si {1}' | xargs -ro yay -S
+}
+
+# +------+
+# | tmux |
+# +------+
+
+fmux() {
+ prj=$(find $XDG_CONFIG_HOME/tmuxp/ -execdir bash -c 'basename "${0%.*}"' {} ';' | sort | uniq | nl | fzf | cut -f 2)
+ echo $prj
+ [ -n "$prj" ] && tmuxp load $prj
+}
+
+# ftmuxp - propose every possible tmuxp session
+ftmuxp() {
+ if [[ -n $TMUX ]]; then
+ return
+ fi
+
+ # get the IDs
+ ID="$(ls $XDG_CONFIG_HOME/tmuxp | sed -e 's/\.yml$//')"
+ if [[ -z "$ID" ]]; then
+ tmux new-session
+ fi
+
+ create_new_session="Create New Session"
+
+ ID="${create_new_session}\n$ID"
+ ID="$(echo $ID | fzf | cut -d: -f1)"
+
+ if [[ "$ID" = "${create_new_session}" ]]; then
+ tmux new-session
+ elif [[ -n "$ID" ]]; then
+ # Change name of urxvt tab to session name
+ printf '\033]777;tabbedx;set_tab_name;%s\007' "$ID"
+ tmuxp load "$ID"
+ fi
+}
+
+# ftmux - help you choose tmux sessions
+ftmux() {
+ if [[ ! -n $TMUX ]]; then
+ # get the IDs
+ ID="`tmux list-sessions`"
+ if [[ -z "$ID" ]]; then
+ tmux new-session
+ fi
+ create_new_session="Create New Session"
+ ID="$ID\n${create_new_session}:"
+ ID="`echo $ID | fzf | cut -d: -f1`"
+ if [[ "$ID" = "${create_new_session}" ]]; then
+ tmux new-session
+ elif [[ -n "$ID" ]]; then
+ printf '\033]777;tabbedx;set_tab_name;%s\007' "$ID"
+ tmux attach-session -t "$ID"
+ else
+ : # Start terminal normally
+ fi
+ fi
+}
+
+# +-------+
+# | Other |
+# +-------+
+
+# List install files for dotfiles
+fdot() {
+ file=$(find "$DOTFILES/install" -exec basename {} ';' | sort | uniq | nl | fzf | cut -f 2)
+ [ -n "$file" ] && "$EDITOR" "$DOTFILES/install/$file"
+}
+
+# List projects
+fwork() {
+ result=$(find ~/workspace/* -type d -prune -exec basename {} ';' | sort | uniq | nl | fzf | cut -f 2)
+ [ -n "$result" ] && cd ~/workspace/$result
+}
+
+# Open pdf with Zathura
+fpdf() {
+ result=$(find -type f -name '*.pdf' | fzf --bind "ctrl-r:reload(find -type f -name '*.pdf')" --preview "pdftotext {} - | less")
+ [ -n "$result" ] && nohup zathura "$result" &> /dev/null & disown
+}
+
+# Open epubs with Zathura
+fepub() {
+ result=$(find -type f -name '*.epub' | fzf --bind "ctrl-r:reload(find -type f -name '*.epub')")
+ [ -n "$result" ] && nohup zathura "$result" &> /dev/null & disown
+}
+
+# Search and find directories in the dir stack
+fpop() {
+ # Only work with alias d defined as:
+
+ # alias d='dirs -v'
+ # for index ({1..9}) alias "$index"="cd +${index}"; unset index
+
+ d | fzf --height="20%" | cut -f 1 | source /dev/stdin
+}
+
+#ip() {
+# emulate -LR zsh
+#
+# if [[ $1 == 'get' ]]; then
+# res=$(curl -s ipinfo.io/ip)
+# echo -n $res | xsel --clipboard
+# echo "copied $res to clipboard"
+# # only run ip if it exists
+# elif (( $+commands[ip] )); then
+# command ip $*
+# fi
+#}
+
+ssh-create() {
+ if [ ! -z "$1" ]; then
+ ssh-keygen -f $HOME/.ssh/$1 -t rsa -N '' -C "$1"
+ chmod 700 $HOME/.ssh/$1*
+ fi
+}
+
+guest() {
+ local guest="$1"
+ shift
+
+ local port
+ if [[ "$#" -ge 2 && "${@: -1}" =~ ^[0-9]+$ ]]; then
+ port="${@: -1}"
+ set -- "${@:1:$(($#-1))}"
+ fi
+
+ if [[ -z "$guest" || "$#" -lt 1 ]]; then
+ echo "Send file(s) or directories to remote machine"
+ echo "Usage: guest <guest-alias> <file-or-directory>... [port]"
+ return 1
+ fi
+
+ # Auto-detect port
+ if [[ -z "$port" ]]; then
+ if nc -z localhost 22220 2>/dev/null; then
+ port=22220
+ elif nc -z localhost 22 2>/dev/null; then
+ port=22
+ else
+ echo "No known SSH port (22220 or 22) is open. Specify a port manually."
+ return 1
+ fi
+ fi
+
+ for src in "$@"; do
+ src="${src/#\~/$HOME}"
+ if [[ ! -e "$src" ]]; then
+ echo "Error: '$src' does not exist."
+ continue
+ fi
+
+ local abs_path dest_dir rel_dir rsync_src rsync_dest
+
+ abs_path=$(realpath "$src")
+ rel_dir="${abs_path#$HOME/}"
+ dest_dir=$(dirname "$rel_dir")
+
+ # Ensure target dir exists remotely
+ ssh -p "$port" "$guest" "mkdir -p ~/$dest_dir"
+
+ if [[ -d "$src" ]]; then
+ # Add trailing slash to copy contents instead of nesting the dir
+ rsync_src="${src%/}/"
+ rsync_dest="~/$rel_dir/"
+ else
+ rsync_src="$src"
+ rsync_dest="~/$dest_dir/"
+ fi
+
+ echo "Sending '$src' to '$guest:$rsync_dest'..."
+ rsync -avz -e "ssh -p $port" "$rsync_src" "$guest:$rsync_dest"
+ done
+}
+historystat() {
+ history 0 | awk '{print $2}' | sort | uniq -c | sort -n -r | head
+}
+
+promptspeed() {
+ for i in $(seq 1 10); do /usr/bin/time zsh -i -c exit; done
+}
+
+
+matrix() {
+ local lines=$(tput lines)
+ cols=$(tput cols)
+
+ # Check if tmux is available
+ if command -v tmux > /dev/null; then
+ # Save the current status setting
+ local status_setting=$(tmux show -g -w -v status)
+
+ # Turn off tmux status
+ tmux set -g status off
+ else
+ echo "tmux is not available. Exiting."
+ return 1
+ fi
+
+ # Function to restore terminal state
+ restore_terminal() {
+ # Clear the screen
+ clear
+
+ # Bring back tmux status to its original setting
+ if command -v tmux > /dev/null; then
+ tmux set -g status "$status_setting"
+ fi
+ }
+
+ trap 'restore_terminal' INT
+
+ awkscript='
+ {
+ letters="abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789@#$%^&*()"
+ lines=$1
+ random_col=$3
+ c=$4
+ letter=substr(letters,c,1)
+ cols[random_col]=0;
+ for (col in cols) {
+ line=cols[col];
+ cols[col]=cols[col]+1;
+ printf "\033[%s;%sH\033[2;32m%s", line, col, letter;
+ printf "\033[%s;%sH\033[1;37m%s\033[0;0H", cols[col], col, letter;
+ if (cols[col] >= lines) {
+ cols[col]=0;
+ }
+ }
+ }
+ '
+
+ echo -e "\e[1;40m"
+ clear
+
+ while :; do
+ echo $lines $cols $(( $RANDOM % $cols)) $(( $RANDOM % 72 ))
+ sleep 0.05
+ done | awk "$awkscript"
+
+ # Restore terminal state
+ restore_terminal
+}
+
+## Reload shell
+function reload() {
+ local compdump_files="$ZDOTDIR/.zcompdump*"
+
+ if ls $compdump_files &> /dev/null; then
+ rm -f $compdump_files
+ fi
+
+ exec $SHELL -l
+}
+## Generate a secure password
+function passgen() {
+ LC_ALL=C tr -dc ${1:-"[:graph:]"} < /dev/urandom | head -c ${2:-20}
+}
+## Encode/Decode string using base64
+function b64e() {
+ echo "$@" | base64
+}
+
+function b64d() {
+ echo "$@" | base64 -D
+}
+# Search through all man pages
+function fman() {
+ man -k . | fzf -q "$1" --prompt='man> ' --preview $'echo {} | tr -d \'()\' | awk \'{printf "%s ", $2} {print $1}\' | xargs -r man' | tr -d '()' | awk '{printf "%s ", $2} {print $1}' | xargs -r man
+}
+# Back up a file. Usage "backupthis <filename>"
+backupthis() {
+ cp -riv $1 ${1}-$(date +%Y%m%d%H%M).backup;
+}
+
+# Spawn a clone of current terminal
+putstate () {
+ declare +x >~/environment.tmp
+ declare -x >>~/environment.tmp
+ echo cd "$PWD" >>~/environment.tmp
+}
+
+getstate () {
+ . ~/environment.tmp
+}
+
+
+# Tmux layout
+openSession () {
+ tmux split-window -h -t
+ tmux split-window -v -t
+ tmux resize-pane -U 5
+}
+
+# archive compress
+compress() {
+ if [[ -n "$1" ]]; then
+ local file=$1
+ shift
+ case "$file" in
+ *.tar ) tar cf "$file" "$*" ;;
+ *.tar.bz2 ) tar cjf "$file" "$*" ;;
+ *.tar.gz ) tar czf "$file" "$*" ;;
+ *.tgz ) tar czf "$file" "$*" ;;
+ *.zip ) zip "$file" "$*" ;;
+ *.rar ) rar "$file" "$*" ;;
+ * ) tar zcvf "$file.tar.gz" "$*" ;;
+ esac
+ else
+ echo 'usage: compress <foo.tar.gz> ./foo ./bar'
+ fi
+}
+
+extract() {
+ if [[ -f "$1" ]] ; then
+ local filename
+ filename=$(basename "$1")
+ local foldername="${filename%%.*}"
+ local fullpath
+ fullpath=$(perl -e 'use Cwd "abs_path";print abs_path(shift)' "$1")
+ local didfolderexist=false
+
+ if [[ -d "$foldername" ]]; then
+ didfolderexist=true
+ read -p "$foldername already exists, do you want to overwrite it? (y/n) " -n 1
+ echo
+ if [[ "$REPLY" =~ ^[Nn]$ ]]; then
+ return
+ fi
+ fi
+
+ mkdir -p "$foldername" && cd "$foldername" || return
+
+ case "$1" in
+ *.tar.bz2) tar xjf "$fullpath" ;;
+ *.tar.gz) tar xzf "$fullpath" ;;
+ *.tar.xz) tar Jxf "$fullpath" ;;
+ *.tar.Z) tar xzf "$fullpath" ;;
+ *.tar) tar xf "$fullpath" ;;
+ *.taz) tar xzf "$fullpath" ;;
+ *.tb2) tar xjf "$fullpath" ;;
+ *.tbz) tar xjf "$fullpath" ;;
+ *.tbz2) tar xjf "$fullpath" ;;
+ *.tgz) tar xzf "$fullpath" ;;
+ *.txz) tar Jxf "$fullpath" ;;
+ *.rar) unrar x -o+ "$fullpath" >/dev/null ;;
+ *.zip) unzip -o "$fullpath" ;;
+ *)
+ echo "'$1' cannot be extracted via extract()" \
+ && cd .. \
+ && ! "$didfolderexist" \
+ && rm -r "$foldername"
+ ;;
+ esac
+ else
+ echo "'$1' is not a valid file"
+ fi
+}
+
+ports() {
+ local result
+ result=$(sudo netstat -tulpn | grep LISTEN)
+ echo "$result" | fzf
+}
+
+trash() {
+ case "$1" in
+ --list)
+ ls -A1 ~/.local/share/Trash/files/
+ ;;
+ --empty)
+ ls -A1 ~/.local/share/Trash/files/ && \rm -rfv ~/.local/share/Trash/files/*
+ ;;
+ --restore)
+ gio trash --restore "$(gio trash --list | fzf | cut -f 1)"
+ ;;
+ --delete)
+ trash_files=$(ls -A ~/.local/share/Trash/files/ | fzf --multi); echo $trash_files | xargs -I {} rm -rf ~/.local/share/Trash/files/{}
+ ;;
+ *)
+ gio trash "$@"
+ ;;
+ esac
+}
+
+what() {
+ type "$1"
+ echo "$PATH"
+}
+
+shutdown() {
+ if [ "$#" -eq 0 ]; then
+ sudo /sbin/shutdown -h now
+ else
+ sudo /sbin/shutdown -h "$@"
+ fi
+}
+
+windowManagerName () {
+ local window=$(
+ xprop -root -notype
+ )
+
+ local identifier=$(
+ echo "${window}" |
+ awk '$1=="_NET_SUPPORTING_WM_CHECK:"{print $5}'
+ )
+
+ local attributes=$(
+ xprop -id "${identifier}" -notype -f _NET_WM_NAME 8t
+ )
+
+ local name=$(
+ echo "${attributes}" |
+ grep "_NET_WM_NAME = " |
+ cut --delimiter=' ' --fields=3 |
+ cut --delimiter='"' --fields=2
+ )
+
+ echo "${name}"
+}
+
+logout() {
+ local wm
+ wm="$(windowManagerName)"
+ if [[ -n "$wm" ]]; then
+ echo "Logging out by killing window manager: $wm"
+ pkill "$wm"
+ else
+ echo "No window manager detected!" >&2
+ fi
+}
+
+# Gentoo
+emg() {
+ if [[ -z "$1" ]]; then
+ echo "Usage: emg [USE_FLAGS] package [package...]"
+ return 1
+ fi
+
+ if [[ "$1" =~ ^[^-].* ]]; then
+ local use_flags="$1"
+ shift
+ sudo USE="$use_flags" emerge -av "$@"
+ else
+ sudo emerge -av "$@"
+ fi
+}
+
+# Remove command from history
+forget () { # Accepts one history line number as argument or search term
+ if [[ -z "$1" ]]; then
+ echo "Usage: hist <history_number> | hist -s <search_term>"
+ return 1
+ fi
+
+ if [[ "$1" == "-s" ]]; then
+ if [[ -z "$2" ]]; then
+ echo "Usage: hist -s <search_term>"
+ return 1
+ fi
+
+ local search_term="$2"
+
+ if [[ "$OSTYPE" == "linux-gnu"* ]]; then
+ LC_ALL=C sed -i "/${search_term}/d" "$HISTFILE" # GNU sed
+ else
+ LC_ALL=C sed -i '' "/${search_term}/d" "$HISTFILE" # BSD/macOS sed
+ fi
+
+ fc -R "$HISTFILE"
+ echo "Deleted all history entries matching '$search_term'."
+ else
+ local num=$1
+ local cmd=$(fc -ln $num $num 2>/dev/null)
+
+ if [[ -z "$cmd" ]]; then
+ echo "No history entry found for index $num"
+ return 1
+ fi
+
+ history -d $num
+
+ local escaped_cmd=$(echo "$cmd" | sed 's/[\/&]/\\&/g')
+
+ if [[ "$OSTYPE" == "linux-gnu"* ]]; then
+ LC_ALL=C sed -i "/${escaped_cmd}/d" "$HISTFILE"
+ else
+ LC_ALL=C sed -i '' "/${escaped_cmd}/d" "$HISTFILE"
+ fi
+
+ fc -R "$HISTFILE"
+ echo "Deleted '$cmd' from history."
+ fi
+}
+
+# Remove hist command itself
+remove_hist_command() {
+ [[ $1 != 'hist '* ]]
+}
+
+remove_hist_command
+
+
+search() {
+ # Search for a pattern in the specified directory (non-recursive).
+ dir="${1:-.}"
+ ls -1 "$dir" | grep -i "$2"
+}
+
+deepsearch() {
+ # Perform a recursive search for a pattern in the specified directory.
+ dir="${1:-.}"
+ find "$dir" -iname "$2"
+}
+
+notes() {
+ local base_dir="$HOME/documents/main/"
+
+ if [[ -z "$1" ]]; then
+ # No argument → cd to notes directory
+ cd "$base_dir" || return
+ return
+ fi
+
+ local target="$1" # The argument itself
+
+ # Use find to check if the file exists anywhere in the base directory
+ local found_files=($(find "$base_dir" -type f -name "$target"))
+
+ if [[ ${#found_files[@]} -eq 1 ]]; then
+ # Only one match found, open it directly
+ $EDITOR "${found_files[0]}"
+ elif [[ ${#found_files[@]} -gt 1 ]]; then
+ # Multiple files found, prompt the user to select one
+ echo "Multiple files found for '$target'. Please choose one:"
+ PS3="Please enter a number to select a file (1-${#found_files[@]}): "
+ select selected_file in "${found_files[@]}"; do
+ if [[ -n "$selected_file" ]]; then
+ $EDITOR "$selected_file"
+ break
+ else
+ echo "Invalid selection, try again."
+ fi
+ done
+ else
+ # If no match found, search for a directory
+ local found_dir=$(find "$base_dir" -type d -name "$target" -print -quit)
+
+ if [[ -n "$found_dir" ]]; then
+ # Directory found, cd into it
+ cd "$found_dir" || return
+ else
+ # If no match found, create the file and open it
+ local full_target="$base_dir/$target"
+ mkdir -p "$(dirname "$full_target")"
+ $EDITOR "$full_target"
+ fi
+ fi
+}
+
+# Enable tab completion for files and directories
+_notes_complete() {
+ local base_dir="$HOME/documents/main"
+ compadd -o nospace -- $(find "$base_dir" -type f -o -type d -printf '%P\n')
+}
+
+compdef _notes_complete notes
+
+
+ship() {
+ local binary_dir="$HOME/.local/share"
+ local bin_symlink_dir="$HOME/.local/bin"
+ local project_dirs=(
+ "$HOME/projects/"
+ "$HOME/src/"
+ "$HOME/src/site/"
+ )
+
+ mkdir -p "$binary_dir" "$bin_symlink_dir"
+
+ local project_dir=""
+
+ if [[ -n "$1" ]]; then
+ # Project name specified
+ for dir in "${project_dirs[@]}"; do
+ if [[ -d "$dir/$1" ]]; then
+ project_dir="$dir/$1"
+ break
+ fi
+ done
+
+ if [[ -z "$project_dir" ]]; then
+ echo "Project '$1' not found."
+ return 1
+ fi
+ else
+ # No argument: pick latest edited
+ local bin_file
+ bin_file=$(find "${project_dirs[@]}" -type f -name "Cargo.toml" -exec stat --format="%Y %n" {} \; 2>/dev/null | sort -nr | head -n1 | cut -d' ' -f2-)
+
+ if [[ -z "$bin_file" ]]; then
+ echo "No Cargo.toml found."
+ return 1
+ fi
+
+ project_dir=$(dirname "$bin_file")
+ fi
+
+ cd "$project_dir" || return
+ echo "Building project in $project_dir..."
+
+ # Build it
+ cargo build --release || { echo "Build failed"; return 1; }
+
+ # Assume binary has same name as project dir
+ local binary_name
+ binary_name=$(basename "$project_dir")
+ local built_binary="target/release/$binary_name"
+
+ if [[ -x "$built_binary" ]]; then
+ echo "Copying $built_binary to $binary_dir/$binary_name"
+ cp "$built_binary" "$binary_dir/$binary_name"
+
+ # Create/Update symlink
+ local symlink_path="$bin_symlink_dir/$binary_name"
+ ln -sf "$binary_dir/$binary_name" "$symlink_path"
+
+ echo "Binary is now at: $binary_dir/$binary_name"
+ echo "Symlink created at: $symlink_path"
+ else
+ echo "Built binary not found: $built_binary"
+ echo "You may need to manually specify the output binary."
+ fi
+}
+
+
+forge() {
+
+ local install=no
+ local usage="Usage: forge [--install]"
+
+ # Handle --install flag
+ if [[ "$1" == "--install" ]]; then
+ install=yes
+ shift
+ elif [[ "$1" == "-h" || "$1" == "--help" ]]; then
+ echo "$usage"
+ return 0
+ fi
+
+ if [[ -f "CMakeLists.txt" ]]; then
+ echo "📦 CMake project detected"
+ [[ ! -d build ]] && mkdir build
+ cmake -B build -DCMAKE_BUILD_TYPE=Release || return 1
+ cmake --build build || return 1
+ [[ "$install" == "yes" ]] && sudo cmake --install build
+
+ elif [[ -f "meson.build" ]]; then
+ echo "📦 Meson project detected"
+ if [[ ! -d build ]]; then
+ meson setup build || return 1
+ fi
+ ninja -C build || return 1
+ [[ "$install" == "yes" ]] && sudo ninja -C build install
+
+ elif [[ -f "Makefile" ]]; then
+ echo "📦 Makefile project detected"
+ # Try `make all`, fallback to `make` if `all` fails
+ if make -q all 2>/dev/null; then
+ make all || return 1
+ else
+ make || return 1
+ fi
+ [[ "$install" == "yes" ]] && sudo make install
+
+ else
+ echo "❌ No supported build system found."
+ return 1
+ fi
+}
+
+# Windows Path:
+windows_home() {
+ for dir in /mnt/windows/Users/*(N); do
+ base=${dir:t} # `:t` is zsh's "tail" = basename
+ if [[ -d $dir && ! $base =~ ^(All Users|Default|Default User|Public|nx|desktop.ini)$ ]]; then
+ echo "$dir"
+ return 0
+ fi
+ done
+ return 1
+}
+
+if winhome_path=$(windows_home); then
+ hash -d winhome="$winhome_path"
+fi
+
+# Allow nnn filemanager to cd on quit
+nnn() {
+ declare -x +g NNN_TMPFILE=$(mktemp --tmpdir $0.XXXX)
+ trap "command rm -f $NNN_TMPFILE" EXIT
+ =nnn $@
+ [ -s $NNN_TMPFILE ] && source $NNN_TMPFILE
+}
diff --git a/common/config/zsh/user/options.zsh b/common/config/zsh/user/options.zsh
new file mode 100644
index 0000000..99840d7
--- /dev/null
+++ b/common/config/zsh/user/options.zsh
@@ -0,0 +1,66 @@
+# Recursion limits
+FUNCNEST=999
+
+DISABLE_MAGIC_FUNCTIONS=true
+
+# Enable various options for Zsh behavior
+setopt interactive_comments # Allow comments to appear in interactive mode
+unsetopt BEEP # Disable the system beep (to prevent annoying beeps)
+setopt extendedglob # Enable extended globbing for complex pattern matching
+setopt nomatch # Prevent errors when a glob pattern doesn't match any files
+setopt notify # Notify when background jobs complete
+setopt completeinword # Allow tab completion within words
+setopt prompt_subst # Allow prompt variables to be substituted
+
+# Enable automatic directory navigation
+setopt autocd # Automatically change to a directory if the directory name is typed alone
+setopt AUTO_PUSHD # Save more directory history, and use "cd -" with tab completion
+
+# Hide history of commands starting with a space
+setopt histignorespace # Do not save commands that start with a space in the history
+
+
+# --- Detect terminal control characters and behavior ---
+
+# Set these to true/false to run on every new tmux/terms
+: ${CHECK_ON_TMUX_CHANGES:=false}
+: ${CHECK_ON_NEW_INSTANCES:=false}
+
+# Fast terminal fingerprinting for optimizing prompt rendering
+function initialize_terminal_fingerprint() {
+ # --- Fast terminal fingerprint ---
+ TERM_BASIC="$TERM-$COLORTERM"
+ TERM_TMUX=""
+ [[ "$CHECK_ON_TMUX_CHANGES" == "true" && -n "$TMUX" ]] && TERM_TMUX="-tmux$TMUX_PANE"
+ TERM_INSTANCE=""
+ [[ "$CHECK_ON_NEW_INSTANCES" == "true" ]] && TERM_INSTANCE="-$$"
+ # Combine fingerprint parts only if they're non-empty (faster than function call)
+ CURRENT_TERM_FINGERPRINT="${TERM_BASIC}${TERM_TMUX}${TERM_INSTANCE}"
+ # Only run detection if terminal has changed (single comparison)
+ if [[ "$CURRENT_TERM_FINGERPRINT" != "$LAST_TERM_FINGERPRINT" ]]; then
+ export LAST_TERM_FINGERPRINT="$CURRENT_TERM_FINGERPRINT"
+ # Fast reset
+ export CTRL_C_SIGINT=false
+ export CTRL_V_PASTE=false
+ # Fast SIGINT check (single command, no pipes)
+ INTR_CHAR=$(stty -a 2>/dev/null | sed -n 's/.*intr = \([^;]*\);.*/\1/p' | tr -d ' ')
+ [[ "$INTR_CHAR" == "^C" ]] && export CTRL_C_SIGINT=true
+ # Check if Ctrl+V is bound to lnext terminal function
+ LNEXT_CHAR=$(stty -a 2>/dev/null | sed -n 's/.*lnext = \([^;]*\);.*/\1/p' | tr -d ' ')
+ # If lnext is NOT ^V, then Ctrl+V might work as paste
+ if [[ "$LNEXT_CHAR" != "^V" ]]; then
+ # Check if clipboard tools exist
+ if [[ -n "$WAYLAND_DISPLAY" && -x "$(command -v wl-paste)" ]]; then
+ export CTRL_V_PASTE=true
+ elif [[ -n "$DISPLAY" && -x "$(command -v xclip)" ]]; then
+ export CTRL_V_PASTE=true
+ fi
+ fi
+ # Print status only if debug is enabled
+ [[ -n "$DEBUG_TERM_DETECT" ]] && echo "Terminal: CTRL_C_SIGINT=$CTRL_C_SIGINT CTRL_V_PASTE=$CTRL_V_PASTE"
+ fi
+}
+
+# Initialize terminal fingerprint on startup
+initialize_terminal_fingerprint
+
diff --git a/common/config/zsh/user/prompt.zsh b/common/config/zsh/user/prompt.zsh
new file mode 100644
index 0000000..c55a835
--- /dev/null
+++ b/common/config/zsh/user/prompt.zsh
@@ -0,0 +1,679 @@
+#!/bin/zsh
+
+########## Prompt(s) ##########
+
+# Autoload necessary functions for vcs_info and coloring
+autoload -Uz vcs_info
+autoload -Uz add-zsh-hook
+autoload -U colors && colors
+
+# Enable prompt substitution
+setopt prompt_subst
+
+# Display git branch status and color
+precmd_vcs_info() { vcs_info }
+
+# Add vcs_info to precmd functions
+precmd_functions+=( precmd_vcs_info )
+
+# Manipulates cursor position: moves down by 2 lines, saves position, and restores cursor after an operation.
+terminfo_down_sc=$terminfo[cud1]$terminfo[cuu1]$terminfo[sc]$terminfo[cud1]
+
+# Track last executed command for exit code display
+typeset -g _last_executed_command=""
+typeset -g _cmd_start_time=0
+typeset -g _cmd_end_time=0
+typeset -g _cmd_duration=0
+typeset -g _spinner_idx=0
+typeset -ga _spinner_frames=('⣾' '⣽' '⣻' '⢿' '⡿' '⣟' '⣯' '⣷')
+typeset -g _cmd_is_running=0
+typeset -g _show_spinner=0
+typeset -g _SPINNER_DELAY=5 # Only show spinner after 5 seconds
+typeset -g _FINISHED_DELAY=10 # Only show finished message after 10 seconds
+
+# Register the ZLE widget for spinner updates - do this early
+zle -N update_spinner
+
+# Cache git information to avoid repeated expensive operations
+typeset -g _git_cached_info=""
+typeset -g _git_cache_timestamp=0
+typeset -g _git_cache_lifetime=2 # seconds before cache expires
+
+# Calculate how much space is available for the prompt components
+function available_space() {
+ local width=${COLUMNS:-80}
+ echo $width
+}
+
+# Check if we need to abbreviate git info
+function need_to_abbreviate_git() {
+ local available=$(available_space)
+ local vi_mode_len=13 # Length of "-- INSERT --"
+ local prompt_base_len=20 # Base prompt elements length
+ local path_len=${#${PWD/#$HOME/\~}}
+ local git_full_len=0
+
+ # Try to estimate git info length if available
+ if git rev-parse --is-inside-work-tree &>/dev/null; then
+ local branch=$(git symbolic-ref --short HEAD 2>/dev/null)
+ git_full_len=${#branch}
+
+ # Add length for status indicators
+ if [[ -n "$(git status --porcelain)" ]]; then
+ # Rough estimate for status text
+ git_full_len=$((git_full_len + 20))
+ fi
+ fi
+
+ # Calculate total space needed
+ local total_needed=$((vi_mode_len + prompt_base_len + path_len + git_full_len))
+
+ # Determine if we need to abbreviate
+ if [[ $total_needed -gt $available ]]; then
+ return 0 # Need to abbreviate
+ else
+ return 1 # Don't need to abbreviate
+ fi
+}
+
+# Custom git branch coloring based on status
+git_branch_test_color() {
+ local now=$(date +%s)
+ local cache_age=$((now - _git_cache_timestamp))
+
+ # Use cached value if available and not expired
+ if [[ -n "$_git_cached_info" && $cache_age -lt $_git_cache_lifetime ]]; then
+ echo "$_git_cached_info"
+ return
+ fi
+
+ local ref=$(git symbolic-ref --short HEAD 2> /dev/null)
+ if [ -n "${ref}" ]; then
+ if [ -n "$(git status --porcelain)" ]; then
+ local gitstatuscolor='%F{green}'
+ else
+ local gitstatuscolor='%F{82}'
+ fi
+ _git_cached_info="${gitstatuscolor}${ref}"
+ _git_cache_timestamp=$now
+ echo "$_git_cached_info"
+ else
+ _git_cached_info=""
+ _git_cache_timestamp=$now
+ echo ""
+ fi
+}
+
+# Git branch with dynamic abbreviation
+git_branch_dynamic() {
+ local now=$(date +%s)
+ local cache_age=$((now - _git_cache_timestamp))
+
+ # Only query git if cache is expired
+ if [[ $cache_age -ge $_git_cache_lifetime ]]; then
+ local ref=$(git symbolic-ref --short HEAD 2> /dev/null)
+ if [ -n "${ref}" ]; then
+ if need_to_abbreviate_git; then
+ # Abbreviated version for small terminals
+ case "${ref}" in
+ "main") _git_cached_info="m" ;;
+ "master") _git_cached_info="m" ;;
+ "development") _git_cached_info="d" ;;
+ "develop") _git_cached_info="d" ;;
+ "feature/"*) _git_cached_info="f/${ref#feature/}" | cut -c 1-4 ;;
+ "release/"*) _git_cached_info="r/${ref#release/}" | cut -c 1-4 ;;
+ "hotfix/"*) _git_cached_info="h/${ref#hotfix/}" | cut -c 1-4 ;;
+ *) _git_cached_info="${ref}" | cut -c 1-5 ;; # Truncate to first 5 chars for other branches
+ esac
+ else
+ # Full branch name when there's room
+ _git_cached_info="${ref}"
+ fi
+ _git_cache_timestamp=$now
+ echo "$_git_cached_info"
+ else
+ _git_cached_info=""
+ _git_cache_timestamp=$now
+ echo ""
+ fi
+ else
+ echo "$_git_cached_info"
+ fi
+}
+
+# VCS info styles (e.g., git)
+zstyle ':vcs_info:*' check-for-changes true
+zstyle ':vcs_info:*' enable git
+
+# Dynamically configure vcs_info formats based on available space
+function configure_vcs_styles() {
+ if need_to_abbreviate_git; then
+ # Abbreviated versions
+ zstyle ':vcs_info:*' stagedstr ' +%F{15}s%f'
+ zstyle ':vcs_info:*' unstagedstr ' -%F{15}u%f'
+ else
+ # Full versions
+ zstyle ':vcs_info:*' stagedstr ' +%F{15}staged%f'
+ zstyle ':vcs_info:*' unstagedstr ' -%F{15}unstaged%f'
+ fi
+
+ zstyle ':vcs_info:*' actionformats '%F{5}%F{2}%b%F{3}|%F{1}%a%F{5}%f '
+ zstyle ':vcs_info:*' formats '%F{208} '$'\uE0A0'' %f$(git_branch_test_color)%f%F{76}%c%F{3}%u%f '
+ zstyle ':vcs_info:git*+set-message:*' hooks git-untracked git-dynamic
+}
+
+# Show "untracked" status in git - with conditional abbreviation
++vi-git-untracked() {
+ if [[ $(git rev-parse --is-inside-work-tree 2> /dev/null) == 'true' ]] && \
+ git status --porcelain | grep '??' &> /dev/null ; then
+
+ if need_to_abbreviate_git; then
+ hook_com[unstaged]+='%F{196} !%f%F{15}u%f'
+ else
+ hook_com[unstaged]+='%F{196} !%f%F{15}untracked%f'
+ fi
+ fi
+}
+
+# Dynamic git branch hook
++vi-git-dynamic() {
+ hook_com[branch]=$(git_branch_dynamic)
+}
+
+# SSH info with conditional abbreviation
+ssh_name() {
+ if [[ -n $SSH_CONNECTION ]]; then
+ local ssh_info
+
+ if need_to_abbreviate_git; then
+ # Abbreviated SSH info
+ ssh_info="ssh:%F{green}%n$nc%f"
+ else
+ ssh_info="ssh:%F{green}%n$nc%f"
+ if [[ -n $SSH_CONNECTION ]]; then
+ local ip_address
+ ip_address=$(echo $SSH_CONNECTION | awk '{print $3}')
+ ssh_info="$ssh_info@%F{green}$ip_address%f"
+ fi
+ fi
+ echo " ${ssh_info}"
+ fi
+}
+
+# Job names (for job control) with conditional abbreviation
+function job_name() {
+ job_name=""
+ job_length=0
+ local available=$(available_space)
+
+ # Only show jobs if we have reasonable space
+ if [ "${available}" -gt 60 ]; then
+ local job_count=$(jobs | wc -l)
+ if [ "${job_count}" -gt 0 ]; then
+ if need_to_abbreviate_git; then
+ job_name+="%F{green}j:${job_count}%f"
+ else
+ local title_jobs="jobs:"
+ job_name="${title_jobs}"
+ job_length=$((${available}-70))
+ [ "${job_length}" -lt "0" ] && job_length=0
+
+ if [ "${job_length}" -gt 0 ]; then
+ job_name+="%F{green}$(jobs | grep + | tr -s " " | cut -d " " -f 4- | cut -b 1-${job_length} | sed "s/\(.*\)/\1/")%f"
+ else
+ job_name+="%F{green}${job_count}%f"
+ fi
+ fi
+ fi
+ fi
+
+ echo "${job_name}"
+}
+
+# Check if we should show the spinner based on elapsed time
+function should_show_spinner() {
+ if [[ $_cmd_is_running -eq 1 ]]; then
+ local current_time=$(date +%s)
+ local elapsed=$((current_time - _cmd_start_time))
+
+ # Show spinner only after delay threshold
+ if [[ $elapsed -ge $_SPINNER_DELAY ]]; then
+ _show_spinner=1
+ return 0 # Yes, show spinner
+ fi
+ fi
+
+ _show_spinner=0
+ return 1 # No, don't show spinner
+}
+
+# Update spinner animation - simplified version
+function update_spinner() {
+ # This function is now just a ZLE widget placeholder
+ # The actual spinner updates happen in the TRAPALRM handler
+ :
+}
+
+# Start spinner timer when command runs longer than threshold
+function start_spinner_timer() {
+ _spinner_idx=0
+ _cmd_is_running=1
+ _show_spinner=0 # Start with spinner hidden until delay passes
+
+ # Set up the TRAPALRM for periodic updates - CRITICAL FIX
+ TMOUT=0.5 # Update spinner every 0.5 seconds
+
+ # Define TRAPALRM function - this is key to the spinner working
+ TRAPALRM() {
+ if [[ $_cmd_is_running -eq 1 ]]; then
+ local current_time=$(date +%s)
+ local elapsed=$((current_time - _cmd_start_time))
+
+ # Show spinner only after delay threshold
+ if [[ $elapsed -ge $_SPINNER_DELAY ]]; then
+ _show_spinner=1
+ _spinner_idx=$(( (_spinner_idx + 1) % ${#_spinner_frames[@]} ))
+
+ # Force prompt refresh - critical for updating the spinner
+ if [[ -o zle ]]; then
+ zle reset-prompt 2>/dev/null || true
+ zle -R
+ fi
+ fi
+ fi
+ }
+}
+
+# Stop spinner when command finishes
+function stop_spinner_timer() {
+ _cmd_is_running=0
+ _show_spinner=0
+
+ # Disable the alarm trap and timer
+ TRAPALRM() { : }
+ TMOUT=0
+
+ # Force prompt refresh to clear spinner
+ if [[ -o zle ]]; then
+ zle reset-prompt 2>/dev/null || true
+ zle -R
+ fi
+}
+
+# Format time in a human-readable way
+function format_time() {
+ local seconds=$1
+ local result=""
+
+ # Format time as hours:minutes:seconds for long durations
+ if [[ $seconds -ge 3600 ]]; then
+ local hours=$((seconds / 3600))
+ local minutes=$(( (seconds % 3600) / 60 ))
+ local secs=$((seconds % 60))
+ result="${hours}h${minutes}m${secs}s"
+ elif [[ $seconds -ge 60 ]]; then
+ local minutes=$((seconds / 60))
+ local secs=$((seconds % 60))
+ result="${minutes}m${secs}s"
+ else
+ result="${seconds}s"
+ fi
+
+ echo "$result"
+}
+
+# Error code display for RPROMPT with spinner - fixed version
+function exit_code_info() {
+ local exit_code=$?
+
+ # If a command is running and we should show spinner
+ if [[ $_cmd_is_running -eq 1 && $_show_spinner -eq 1 ]]; then
+ local spinner=${_spinner_frames[$_spinner_idx]}
+ local current_time=$(date +%s)
+ local elapsed=$((current_time - _cmd_start_time))
+ echo "%F{yellow}${spinner} ${elapsed}s%f"
+ return
+ fi
+
+ # Don't show error code when line editor is active (user is typing)
+ if [[ -o zle ]]; then
+ echo ""
+ return
+ fi
+
+ # Show command finished message for completed commands that took longer than threshold
+ if [[ -n "$_last_executed_command" && $_cmd_duration -ge $_FINISHED_DELAY ]]; then
+ local duration_formatted=$(format_time $_cmd_duration)
+
+ # Show error code along with finished message if there was an error
+ if [[ $exit_code -ne 0 ]]; then
+ # Show TSTP (148) as a suspension indicator instead of error
+ if [[ $exit_code -eq 148 ]]; then
+ echo "%F{cyan}finished ${duration_formatted}%f %F{yellow}⏸ TSTP%f"
+ return
+ fi
+
+ local signal_name=""
+ # Check if it's a signal
+ if [[ $exit_code -gt 128 && $exit_code -le 165 ]]; then
+ local signal_num=$((exit_code - 128))
+ signal_name=$(kill -l $signal_num 2>/dev/null)
+ if [[ -n "$signal_name" ]]; then
+ signal_name=" ($signal_name)"
+ fi
+ fi
+
+ # Return formatted error code with finished message
+ echo "%F{cyan}finished ${duration_formatted}%f %F{red}✘ $exit_code$signal_name%f"
+ else
+ echo "%F{cyan}finished ${duration_formatted}%f %F{green}✓%f"
+ fi
+ return
+ fi
+
+ # Don't show anything for exit code 0 (success) if this is first command
+ if [[ -z "$_last_executed_command" && $exit_code -eq 0 ]]; then
+ echo ""
+ return
+ fi
+
+ # Show TSTP (148) as a suspension indicator instead of error
+ if [[ $exit_code -eq 148 ]]; then
+ echo "%F{yellow}⏸ TSTP%f"
+ return
+ fi
+
+ if [[ $exit_code -ne 0 ]]; then
+ local signal_name=""
+
+ # Check if it's a signal
+ if [[ $exit_code -gt 128 && $exit_code -le 165 ]]; then
+ local signal_num=$((exit_code - 128))
+ signal_name=$(kill -l $signal_num 2>/dev/null)
+ if [[ -n "$signal_name" ]]; then
+ signal_name=" ($signal_name)"
+ fi
+ fi
+
+ # Return formatted error code
+ echo "%F{red}✘ $exit_code$signal_name%f"
+ else
+ echo "%F{green}✓%f" # Success indicator
+ fi
+}
+
+abbreviated_path() {
+ local full_path="${PWD/#$HOME/~}" # Replace $HOME with ~
+ local available=$(available_space)
+
+ # If path is root
+ if [[ "$full_path" == "/" ]]; then
+ echo "%F{4}/%f"
+ return
+ fi
+
+ # If path is just ~
+ if [[ "$full_path" == "~" ]]; then
+ echo "%F{4}~%f"
+ return
+ fi
+
+ # If extremely small terminal, show nothing to avoid breaking prompt
+ if (( available < 20 )); then
+ echo ""
+ return
+ fi
+
+ # For very narrow terminals, just show the current dir
+ if (( available < 30 )); then
+ echo "%F{4}%1~%f"
+ return
+ fi
+
+ # For moderately narrow terminals, show last two components
+ if (( available < 40 )); then
+ echo "%F{4}%2~%f"
+ return
+ fi
+
+ # For wide terminals, show full path
+ if (( available > 70 )); then
+ echo "%F{4}${full_path}%f"
+ return
+ fi
+
+ # Otherwise, show abbreviated path (e.g. ~/d/p/n)
+ local parts=("${(s:/:)full_path}")
+ local result=""
+ local last_index=${#parts[@]}
+
+ for i in {1..$((last_index - 1))}; do
+ [[ -n ${parts[i]} ]] && result+="/${parts[i]:0:1}"
+ done
+
+ result+="/${parts[last_index]}"
+ echo "%F{4}${result}%f"
+}
+
+
+# Prompt variables
+user="%n"
+at="%F{15}at%{$reset_color%}"
+machine="%F{4}%m%{$reset_color%}"
+relative_home="%F{4}%~%{$reset_color%}"
+carriage_return=""$'\n'""
+empty_line_bottom=""
+chevron_right=""
+color_reset="%{$(tput sgr0)%}"
+color_yellow="%{$(tput setaf 226)%}"
+color_blink="%{$(tput blink)%}"
+prompt_symbol="$"
+dollar_sign="${color_yellow}${color_blink}${prompt_symbol}${color_reset}"
+dollar="%(?:%F{2}${dollar_sign}:%F{1}${dollar_sign})"
+space=" "
+#thin_space=$'\u2009'
+thin_space=$'\u202F'
+cmd_prompt="%(?:%F{2}${chevron_right} :%F{1}${chevron_right} )"
+git_info="\$vcs_info_msg_0_"
+v1="%{┌─[%}"
+v2="%{]%}"
+v3="└──["
+v4="]"
+newline=$'\n'
+
+# Indicate INSERT mode for vi - NEVER truncate this
+function insert-mode () {
+ echo "-- INSERT --"
+}
+
+# Indicate NORMAL mode for vi - NEVER truncate this
+function normal-mode () {
+ echo "-- NORMAL --"
+}
+
+# Vi mode indicator
+vi-mode-indicator () {
+ if [[ ${KEYMAP} == vicmd || ${KEYMAP} == vi-cmd-mode ]]; then
+ echo -ne '\e[1 q'
+ vi_mode=$(normal-mode)
+ elif [[ ${KEYMAP} == main || ${KEYMAP} == viins || ${KEYMAP} == '' ]]; then
+ echo -ne '\e[5 q'
+ vi_mode=$(insert-mode)
+ fi
+}
+
+# Prompt function to ensure the prompt stays on one line, even in narrow terminals
+function set-prompt() {
+ vi-mode-indicator
+ configure_vcs_styles # Dynamically set vcs styles based on available space
+ vcs_info # Refresh vcs info with new styles
+
+ local available=$(available_space)
+ if (( available < 14 )); then
+ # Extremely narrow terminal — use minimal prompt
+ PS1="${carriage_return}${dollar}${space}${empty_line_bottom}"
+ RPROMPT='$(exit_code_info)'
+
+ else
+ # Path display - always show something for path, but adapt based on space
+ local path_display="$(abbreviated_path)"
+
+ # Git info - omit entirely if not enough space
+ local gitinfo=""
+ if [[ $available -gt 40 ]]; then
+ gitinfo="${vcs_info_msg_0_}"
+ fi
+
+ # Jobs info
+ local jobs=" $(job_name)"
+
+ # SSH info
+ local sshinfo="$(ssh_name)"
+
+ # Vi mode is priority 1 - ALWAYS show it
+ mode="%F{145}%{$terminfo_down_sc$vi_mode$terminfo[rc]%f%}"
+
+ # Right prompt for error codes or spinner
+ RPROMPT='$(exit_code_info)'
+
+ PS1="${newline}${v1}${user}${v2} ${path_display}${gitinfo}${jobs}${sshinfo}${carriage_return}${mode}${v3}${dollar}${v4}${empty_line_bottom}"
+ fi
+}
+
+# Pre-command hook to set prompt
+my_precmd() {
+ # Calculate command duration if a command was run
+ if [[ -n "$_last_executed_command" && $_cmd_start_time -gt 0 ]]; then
+ _cmd_end_time=$(date +%s)
+ _cmd_duration=$((_cmd_end_time - _cmd_start_time))
+ else
+ _cmd_duration=0
+ fi
+
+ stop_spinner_timer # Make sure spinner is stopped
+ vcs_info
+ set-prompt
+ vi-mode-indicator
+}
+
+add-zsh-hook precmd my_precmd
+
+# Update mode file based on current mode
+update-mode-file() {
+ set-prompt
+ local current_mode=$(cat ~/.vi-mode 2>/dev/null || echo "")
+ local new_mode="$vi_mode"
+
+ if [[ "$new_mode" != "$current_mode" ]]; then
+ echo "$new_mode" >| ~/.vi-mode
+ fi
+
+ # Ensure we're in an interactive shell and ZLE is active
+ if [[ -o zle ]] && zle -l &>/dev/null; then
+ zle reset-prompt 2>/dev/null || true
+ else
+ # If ZLE is not active, fallback and print the prompt manually
+ set-prompt
+ print -Pn "$PS1"
+ fi
+
+ # Refresh tmux client if tmux is running
+ if command -v tmux &>/dev/null && [[ -n "$TMUX" ]]; then
+ tmux refresh-client -S
+ fi
+}
+
+# Check if nvim is running and update mode
+function check-nvim-running() {
+ if pgrep -x "nvim" > /dev/null; then
+ vi_mode=""
+ update-mode-file
+ if command -v tmux &>/dev/null && [[ -n "$TMUX" ]]; then
+ tmux refresh-client -S
+ fi
+ else
+ if [[ ${KEYMAP} == vicmd || ${KEYMAP} == vi-cmd-mode ]]; then
+ vi_mode=$(normal-mode)
+ elif [[ ${KEYMAP} == main || ${KEYMAP} == viins || ${KEYMAP} == '' ]]; then
+ vi_mode=$(insert-mode)
+ fi
+ update-mode-file
+ if command -v tmux &>/dev/null && [[ -n "$TMUX" ]]; then
+ tmux refresh-client -S
+ fi
+ fi
+}
+
+# ZLE line initialization hook
+function zle-line-init() {
+ zle reset-prompt
+ vi-mode-indicator
+ case "${KEYMAP}" in
+ vicmd)
+ echo -ne '\e[1 q'
+ ;;
+ main|viins|*)
+ echo -ne '\e[5 q'
+ ;;
+ esac
+}
+
+# ZLE keymap select hook
+function zle-keymap-select() {
+ update-mode-file
+ zle reset-prompt
+ zle -R
+ vi-mode-indicator
+ case "${KEYMAP}" in
+ vicmd)
+ echo -ne '\e[1 q'
+ ;;
+ main|viins|*)
+ echo -ne '\e[5 q'
+ ;;
+ esac
+}
+
+# Safer version of zle reset-prompt
+function safe_reset_prompt() {
+ # Only reset if ZLE is active
+ if [[ -o zle ]] && zle -l &>/dev/null; then
+ zle reset-prompt 2>/dev/null || true
+ fi
+}
+
+# Preexec hook for command execution - NO BACKGROUND JOBS VERSION
+function preexec() {
+ # Store the command being executed
+ _last_executed_command=$1
+ _cmd_start_time=$(date +%s)
+ _cmd_is_running=1
+ _show_spinner=0 # Reset spinner flag
+
+ # Start the spinner timer immediately
+ start_spinner_timer
+
+ print -rn -- $terminfo[el]
+ echo -ne '\e[5 q'
+ vi-mode-indicator
+}
+
+# Terminal resizing: resets the prompt if ZLE is active, updates the mode file.
+TRAPWINCH() {
+ if [[ -o zle ]] && zle -l &>/dev/null; then
+ zle -R
+ zle reset-prompt 2>/dev/null || true
+ fi
+ update-mode-file 2>/dev/null
+}
+
+# Register ZLE hooks
+zle -N zle-line-init
+zle -N zle-keymap-select
+zle -N update_spinner
+
+# Register hooks
+add-zsh-hook preexec preexec
+add-zsh-hook precmd my_precmd
+
+set-prompt
diff --git a/common/config/zsh/user/prompt_minimal.zsh b/common/config/zsh/user/prompt_minimal.zsh
new file mode 100644
index 0000000..0389e7d
--- /dev/null
+++ b/common/config/zsh/user/prompt_minimal.zsh
@@ -0,0 +1,295 @@
+# vim:ft=zsh ts=2 sw=2 sts=2
+#=#=#=
+# simle_is_power theme
+# folked from agnoster's Theme - https://gist.github.com/3712874
+#
+# In order for this theme to render correctly, you will need a
+# [Powerline-patched font](https://github.com/Lokaltog/powerline-fonts).
+#=#=
+#==============================================================================
+# Color setting {{{
+#==============================================================================
+
+setopt prompt_subst
+
+bg_dir=240
+bg_dark=237
+fg_red=210
+
+#===========================================================================}}}
+# Segment drawing {{{
+#==============================================================================
+# A few utility functions to make it easy and re-usable to draw segmented prompts
+
+CURRENT_BG='NONE'
+# SEGMENT_SEPARATOR=''
+SEGMENT_SEPARATOR=''
+# SEGMENT_SEPARATOR=''
+# SEGMENT_SEPARATOR='▒'
+# SEGMENT_SEPARATOR='▓▒░'
+
+# Begin a segment
+# Takes two arguments, background and foreground. Both can be omitted,
+# rendering default background/foreground.
+prompt_segment() {
+ local bg fg
+ [[ -n $1 ]] && bg="%K{$1}" || bg="%k"
+ [[ -n $2 ]] && fg="%F{$2}" || fg="%f"
+ if [[ $CURRENT_BG != 'NONE' && $1 != $CURRENT_BG ]]; then
+ echo -n " %{$bg%F{$CURRENT_BG}%}$SEGMENT_SEPARATOR%{$fg%} "
+ else
+ echo -n "%{$bg%}%{$fg%} "
+ fi
+ CURRENT_BG=$1
+ [[ -n $3 ]] && echo -n $3
+}
+
+# End the prompt, closing any open segments
+prompt_end() {
+ if [[ -n $CURRENT_BG ]]; then
+ echo -n " %{%k%F{$CURRENT_BG}%}$SEGMENT_SEPARATOR"
+ else
+ echo -n "%{%k%}"
+ fi
+ echo -n "%{%f%}"
+ CURRENT_BG=''
+}
+
+#===========================================================================}}}
+# Prompt components {{{
+#==============================================================================
+# Each component will draw itself, and hide itself if no information needs to be shown
+#------------------------------------------------------------------------------
+# Init: {{{
+#------------------------------------------------------------------------------
+
+prompt_init() {
+ echo -n "%{%F{240}%K{240}%}"
+}
+
+#---------------------------------------------------------------------------}}}
+# Status: {{{
+#------------------------------------------------------------------------------
+# - was there an error
+# - am I root
+# - are there background jobs?
+# - am I in ranger subshell?
+
+prompt_status() {
+ local symbols
+ symbols=()
+ [[ $RETVAL -ne 0 ]] && symbols+="%{%F{${fg_red}}%}✞"
+ [[ $UID -eq 0 ]] && symbols+="%{%F{223}%}⚡"
+ [[ $(jobs -l | wc -l) -gt 0 ]] && symbols+="%{%F{cyan}%}⚙"
+ [[ -n ${RANGER_LEVEL} ]] && symbols+="%{%F{153}%}®"
+
+ [[ -n "$symbols" ]] && prompt_segment ${bg_dark} NONE "$symbols"
+}
+
+#---------------------------------------------------------------------------}}}
+# Virtualenv: current working virtualenv {{{
+#------------------------------------------------------------------------------
+
+prompt_virtualenv() {
+ local virtualenv_path="$VIRTUAL_ENV"
+ if [[ -n $virtualenv_path && -n $VIRTUAL_ENV_DISABLE_PROMPT ]]; then
+ prompt_segment green black "(`basename $virtualenv_path`)"
+ fi
+}
+
+#---------------------------------------------------------------------------}}}
+# Dir: current working directory {{{
+#------------------------------------------------------------------------------
+
+prompt_dir() {
+ prompt_segment ${bg_dir} 231 '%~'
+}
+
+#---------------------------------------------------------------------------}}}
+# Git: branch/detached head, dirty status {{{
+#------------------------------------------------------------------------------
+
+prompt_git() {
+ local ref dirty mode repo_path
+ repo_path=$(git rev-parse --git-dir 2>/dev/null)
+
+ if $(git rev-parse --is-inside-work-tree >/dev/null 2>&1); then
+ # dirty=$(parse_git_dirty)
+ ref=$(git symbolic-ref HEAD 2> /dev/null) || ref="➔ $(git show-ref --head -s --abbrev |head -n1 2> /dev/null)"
+ # if [[ -n $dirty ]]; then
+ # prompt_segment ${bg_dark} 223
+ # else
+ prompt_segment ${bg_dark} 153
+ # fi
+
+ if [[ -e "${repo_path}/BISECT_LOG" ]]; then
+ mode=" <B>"
+ elif [[ -e "${repo_path}/MERGE_HEAD" ]]; then
+ mode=" >M<"
+ elif [[ -e "${repo_path}/rebase" || -e "${repo_path}/rebase-apply" || -e "${repo_path}/rebase-merge" || -e "${repo_path}/../.dotest" ]]; then
+ mode=" >R>"
+ fi
+
+ autoload -Uz vcs_info
+
+ zstyle ':vcs_info:*' enable git
+ zstyle ':vcs_info:*' get-revision true
+ zstyle ':vcs_info:*' check-for-changes true
+ zstyle ':vcs_info:*' stagedstr '+'
+ zstyle ':vcs_info:git:*' unstagedstr '*'
+ zstyle ':vcs_info:*' formats ' %u%c'
+ zstyle ':vcs_info:*' actionformats ' %u%c'
+ vcs_info
+ echo -n "${ref/refs\/heads\// }${vcs_info_msg_0_%% }${mode}"
+ fi
+}
+
+#---------------------------------------------------------------------------}}}
+# Hg: prompt {{{
+#------------------------------------------------------------------------------
+
+prompt_hg() {
+ local rev status
+ if $(hg id >/dev/null 2>&1); then
+ if $(hg prompt >/dev/null 2>&1); then
+ if [[ $(hg prompt "{status|unknown}") = "?" ]]; then
+ # if files are not added
+ prompt_segment ${fg_red} ${bg_dark}
+ st='±'
+ elif [[ -n $(hg prompt "{status|modified}") ]]; then
+ # if any modification
+ prompt_segment 223 ${bg_dark}
+ st='±'
+ else
+ # if working copy is clean
+ prompt_segment 153 ${bg_dark}
+ fi
+ echo -n $(hg prompt "☿ {rev}@{branch}") $st
+ else
+ st=""
+ rev=$(hg id -n 2>/dev/null | sed 's/[^-0-9]//g')
+ branch=$(hg id -b 2>/dev/null)
+ if `hg st | grep -q "^\?"`; then
+ prompt_segment ${fg_red} ${bg_dark}
+ st='±'
+ elif `hg st | grep -q "^(M|A)"`; then
+ prompt_segment 223 ${bg_dark}
+ st='±'
+ else
+ prompt_segment 153 ${bg_dark}
+ fi
+ echo -n "☿ $rev@$branch" $st
+ fi
+ fi
+}
+
+#}}}========================================================================}}}
+# Build main prompt {{{
+#==============================================================================
+
+
+function vi-mode-indicator() {
+ local current_mode
+ current_mode=$(cat ~/.vi-mode 2>/dev/null || echo "")
+
+ if [[ ${KEYMAP} == vicmd || ${KEYMAP} == vi-cmd-mode ]]; then
+ [[ "$current_mode" != "-- NORMAL --" ]] && echo "-- NORMAL --" >| ~/.vi-mode
+ elif [[ ${KEYMAP} == main || ${KEYMAP} == viins || ${KEYMAP} == '' ]]; then
+ [[ "$current_mode" != "-- INSERT --" ]] && echo "-- INSERT --" >| ~/.vi-mode
+ fi
+}
+
+build_prompt() {
+ RETVAL=$?
+ vi-mode-indicator
+ prompt_init
+ prompt_virtualenv
+ prompt_dir
+ prompt_git
+ prompt_hg
+ prompt_status
+ prompt_end
+}
+
+color_reset="%{$(tput sgr0)%}"
+color_yellow="%{$(tput setaf 226)%}"
+color_blink="%{$(tput blink)%}"
+prompt_symbol="$"
+dollar_sign="${color_yellow}${color_blink}${prompt_symbol}${color_reset}"
+dollar="%(?:%F{2}${dollar_sign}:%F{1}${dollar_sign})"
+
+v1="%{┌─[%}"
+v2="%{]%}"
+v3="└─["
+v4="]"
+user="%n"
+
+PROMPT="${v1}${user}%f%b%k${v2}$(build_prompt)$reset_color
+${v3}${dollar}${v4}${empty_line_bottom}$reset_color"
+#PROMPT='%n@%m:%~%# '
+#%{%F{240}%}\$ %{$reset_color%}'
+#%{${dollar}%} %{$reset_color%}'
+RPROMPT=''
+
+PROMPT2='%{%F{30}%}↪%{$reset_color%} '
+RPROMPT2='%{$fg_bold[green]%}%_%{$reset_color%}'
+
+function update-mode-file() {
+ local current_mode=$(cat ~/.vi-mode 2>/dev/null || echo "")
+ local new_mode="$vi_mode"
+
+ # Check if the mode is different before updating
+ if [[ "$new_mode" != "$current_mode" ]]; then
+ echo "$new_mode" >| ~/.vi-mode
+ fi
+
+ # Only call zle if ZLE is active
+ if [[ -o zle ]]; then
+ zle reset-prompt # Force refresh
+ fi
+
+ # Ensure tmux client refresh only happens if tmux is running
+ if command -v tmux &>/dev/null && [[ -n "$TMUX" ]]; then
+ tmux refresh-client -S
+ fi
+}
+function zle-line-init() {
+ zle reset-prompt
+ case "${KEYMAP}" in
+ vicmd)
+ echo -ne '\e[1 q'
+ ;;
+ main|viins|*)
+ echo -ne '\e[5 q'
+ ;;
+ esac
+}
+function zle-keymap-select() {
+ local current_keymap
+ current_keymap="${KEYMAP}"
+
+ update-mode-file
+ zle reset-prompt
+
+ case "$current_keymap" in
+ vicmd)
+ echo -ne '\e[1 q'
+ ;;
+ main|viins|*)
+ echo -ne '\e[5 q'
+ ;;
+ esac
+}
+
+precmd () {
+ print -rP
+}
+
+preexec () {
+ print -rn -- $terminfo[el]
+ echo -ne '\e[5 q' # Reset cursor shape
+}
+zle -N zle-line-init
+zle -N zle-keymap-select
+
+#===========================================================================}}}
diff --git a/common/config/zsh/user/prompt_new.zsh b/common/config/zsh/user/prompt_new.zsh
new file mode 100644
index 0000000..78791ef
--- /dev/null
+++ b/common/config/zsh/user/prompt_new.zsh
@@ -0,0 +1,863 @@
+# __ _ __ _| | _____ ______ _| | __
+# / _` |/ _` | |/ / _ \_ / _` | |/ /
+# | (_| | (_| | < (_) / / (_| | <
+# \__,_|\__, |_|\_\___/___\__,_|_|\_\
+# |___/
+#
+# An asynchronous, dynamic color prompt for ZSH with Git, vi mode, and exit
+# status indicators
+#
+#
+# MIT License
+#
+# Copyright (c) 2017-2019 Alexandros Kozak
+#
+# Permission is hereby granted, free of charge, to any person obtaining a copy
+# of this software and associated documentation files (the "Software"), to deal
+# in the Software without restriction, including without limitation the rights
+# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
+# copies of the Software, and to permit persons to whom the Software is
+# furnished to do so, subject to the following conditions:
+#
+# The above copyright notice and this permission notice shall be included in all
+# copies or substantial portions of the Software.
+#
+# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
+# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
+# SOFTWARE.
+#
+#
+# https://github.com/agkozak/agkozak-zsh-prompt
+#
+
+# shellcheck disable=SC1090,SC2016,SC2034,SC2088,SC2148,SC2154,SC2190
+
+# psvar[] Usage
+#
+# psvar Index Prompt String Equivalent Usage
+#
+# psvar[1] %1v Hostname/abbreviated hostname (only
+# displayed for SSH connections)
+# psvar[2] %2v Working directory or abbreviation
+# thereof
+# psvar[3] %3v Current working Git branch, along
+# with indicator of changes made
+# psvar[4] %4v Equals 'vicmd' when vi command mode
+# is enabled; otherwise empty
+
+# Set AGKOZAK_PROMPT_DEBUG=1 to see debugging information
+AGKOZAK_PROMPT_DEBUG=${AGKOZAK_PROMPT_DEBUG:-0}
+
+############################################################
+# Display a message on STDERR if debug mode is enabled
+#
+# Globals:
+# AGKOZAK_PROMPT_DEBUG
+# Arguments:
+# $1 Message to send to STDERR
+############################################################
+_agkozak_debug_print() {
+ (( AGKOZAK_PROMPT_DEBUG )) && print -- "agkozak-zsh-prompt: $1" >&2
+}
+
+if (( AGKOZAK_PROMPT_DEBUG )); then
+ autoload -Uz is-at-least
+
+ setopt WARN_CREATE_GLOBAL
+
+ if is-at-least 5.4.0; then
+ setopt WARN_NESTED_VAR
+ fi
+fi
+
+# Set AGKOZAK_PROMPT_DIRTRIM to the desired number of directory elements to
+# display, or set it to 0 for no directory trimming
+typeset -g AGKOZAK_PROMPT_DIRTRIM=${AGKOZAK_PROMPT_DIRTRIM:-2}
+
+# Set AGKOZAK_MULTILINE to 0 to enable the legacy, single-line prompt
+typeset -g AGKOZAK_MULTILINE=${AGKOZAK_MULTILINE:-1}
+
+# Set AGKOZAK_LEFT_PROMPT_ONLY to have the Git status appear in the left prompt
+typeset -g AGKOZAK_LEFT_PROMPT_ONLY=${AGKOZAK_LEFT_PROMPT_ONLY:-0}
+
+# Set AGKOZAK_COLORS_* variables to any valid color
+# AGKOZAK_COLORS_EXIT_STATUS changes the exit status color (default: red)
+# AGKOZAK_COLORS_USER_HOST changes the username/hostname color (default: green)
+# AGKOZAK_COLORS_PATH changes the path color (default: blue)
+# AGKOZAK_COLORS_BRANCH_STATUS changes the branch status color (default: yellow)
+# AGKOZAK_COLORS_PROMPT_CHAR changes the prompt character color (default: white)
+typeset -g AGKOZAK_COLORS_EXIT_STATUS=${AGKOZAK_COLORS_EXIT_STATUS:-red}
+typeset -g AGKOZAK_COLORS_USER_HOST=${AGKOZAK_COLORS_USER_HOST:-green}
+typeset -g AGKOZAK_COLORS_PATH=${AGKOZAK_COLORS_PATH:-blue}
+typeset -g AGKOZAK_COLORS_BRANCH_STATUS=${AGKOZAK_COLORS_BRANCH_STATUS:-yellow}
+
+setopt PROMPT_SUBST NO_PROMPT_BANG
+
+######################################################################
+# GENERAL FUNCTIONS
+######################################################################
+
+############################################################
+# Are colors available?
+#
+# Globals:
+# AGKOZAK_HAS_COLORS
+############################################################
+_agkozak_has_colors() {
+ if (( $+AGKOZAK_HAS_COLORS )); then
+ :
+ else
+ case $TERM in
+ *-256color) typeset -g AGKOZAK_HAS_COLORS=1 ;;
+ vt100|dumb) typeset -g AGKOZAK_HAS_COLORS=0 ;;
+ *)
+ local colors
+ case $OSTYPE in
+ freebsd*|dragonfly*) colors=$(tput Co) ;;
+ *) colors=$(tput colors) ;;
+ esac
+ typeset -g AGKOZAK_HAS_COLORS=$(( colors >= 8 ))
+ ;;
+ esac
+ fi
+ (( AGKOZAK_HAS_COLORS ))
+}
+
+############################################################
+# Is the user connected via SSH?
+#
+# This function works perfectly for regular users. It is
+# nearly impossible to detect with accuracy how a superuser
+# is connected, so this prompt opts simply to display his or
+# her username and hostname in inverse video.
+############################################################
+_agkozak_is_ssh() {
+ [[ -n "${SSH_CONNECTION-}${SSH_CLIENT-}${SSH_TTY-}" ]]
+}
+
+############################################################
+# Emulation of bash's PROMPT_DIRTRIM for ZSH
+#
+# Take PWD and substitute HOME with `~'. If the rest of PWD
+# has more than a certain number of elements in its
+# directory tree, keep the number specified by
+# AGKOZAK_PROMPT_DIRTRIM (default: 2) and abbreviate the
+# rest with `...'. (Set AGKOZAK_PROMPT_DIRTRIM=0 to disable
+# directory trimming). For example,
+#
+# $HOME/dotfiles/polyglot/img
+#
+# will be displayed as
+#
+# ~/.../polyglot/img
+#
+# Named directories will by default be displayed using their
+# aliases in the prompt (e.g. `~project'). Set
+# AGKOZAK_NAMED_DIRS=0 to have them displayed just like any
+# other directory.
+#
+# Globals:
+# AGKOZAK_NAMED_DIRS
+# Arguments:
+# $@ [Optional] If `-v', store the function's output in
+# psvar[2] instead of printing it to STDOUT
+# $@ Number of directory elements to display (default: 2)
+############################################################
+_agkozak_prompt_dirtrim() {
+ # Process arguments
+ local argument
+ for argument in $@; do
+ [[ $argument == '-v' ]] && local var=1
+ done
+ until [[ $1 != '-v' ]]; do
+ shift
+ done
+ [[ $1 -ge 0 ]] || set 2
+
+ # Default behavior (when AGKOZAK_NAMED_DIRS is 1)
+ typeset -g AGKOZAK_NAMED_DIRS=${AGKOZAK_NAMED_DIRS:-1}
+ if (( AGKOZAK_NAMED_DIRS )); then
+ local zsh_pwd
+ print -Pnz '%~'
+
+ # IF AGKOZAK_PROMPT_DIRTRIM is not 0, trim directory
+ if (( $1 )); then
+ read -rz zsh_pwd
+ case $zsh_pwd in
+ \~) print -Pnz $zsh_pwd ;;
+ \~/*) print -Pnz "%($(( $1 + 2 ))~|~/.../%${1}~|%~)" ;;
+ \~*) print -Pnz "%($(( $1 + 2 ))~|${zsh_pwd%%${zsh_pwd#\~*\/}}.../%${1}~|%~)" ;;
+ *) print -Pnz "%($(( $1 + 1 ))/|.../%${1}d|%d)" ;;
+ esac
+ fi
+
+ # If AGKOZAK_NAMED_DIRS is 0
+ else
+ local dir dir_count
+ case $HOME in
+ /) dir=${PWD} ;;
+ *) dir=${PWD#$HOME} ;;
+ esac
+
+ # If AGKOZAK_PROMPT_DIRTRIM is not 0, trim the directory
+ if (( $1 > 0 )); then
+
+ # The number of directory elements is the number of slashes in ${PWD#$HOME}
+ dir_count=$(( ${#dir} - ${#${dir//\//}} ))
+ if (( dir_count <= $1 )); then
+ case $PWD in
+ ${HOME}) print -nz '~' ;;
+ ${HOME}*) print -nz "~${dir}" ;;
+ *) print -nz "$PWD" ;;
+ esac
+ else
+ local lopped_path i
+ lopped_path=${dir}
+ i=0
+ while (( i != $1 )); do
+ lopped_path=${lopped_path%\/*}
+ (( i++ ))
+ done
+ case $PWD in
+ ${HOME}*) print -nz "~/...${dir#${lopped_path}}" ;;
+ *) print -nz -f '...%s' "${PWD#${lopped_path}}" ;;
+ esac
+ fi
+
+ # If AGKOZAK_PROMPT_DIRTRIM is 0
+ else
+ case $PWD in
+ ${HOME}) print -nz '~' ;;
+ ${HOME}*) print -nz "~${dir}" ;;
+ *) print -nz "$PWD" ;;
+ esac
+ fi
+ fi
+
+ local output
+ read -rz output
+
+ # Argument -v stores the output to psvar[2]; otherwise send to STDOUT
+ if (( var )); then
+ psvar[2]=$output
+ else
+ print $output
+ fi
+}
+
+############################################################
+# Display current branch name, followed by symbols
+# representing changes to the working copy
+############################################################
+_agkozak_branch_status() {
+ local ref branch
+ ref=$(command git symbolic-ref --quiet HEAD 2> /dev/null)
+ case $? in # See what the exit code is.
+ 0) ;; # $ref contains the name of a checked-out branch.
+ 128) return ;; # No Git repository here.
+ # Otherwise, see if HEAD is in detached state.
+ *) ref=$(command git rev-parse --short HEAD 2> /dev/null) || return ;;
+ esac
+ branch=${ref#refs/heads/}
+
+ if [[ -n $branch ]]; then
+ local git_status symbols i=1 k
+ git_status="$(LC_ALL=C command git status 2>&1)"
+
+ typeset -A messages
+ messages=(
+ '&*' ' have diverged,'
+ '&' 'Your branch is behind '
+ '*' 'Your branch is ahead of '
+ '+' 'new file: '
+ 'x' 'deleted: '
+ '!' 'modified: '
+ '>' 'renamed: '
+ '?' 'Untracked files:'
+ )
+
+ for k in '&*' '&' '*' '+' 'x' '!' '>' '?'; do
+ case $git_status in
+ *${messages[$k]}*) symbols+="${AGKOZAK_CUSTOM_SYMBOLS[$i]:-$k}" ;;
+ esac
+ (( i++ ))
+ done
+
+ [[ -n $symbols ]] && symbols=" ${symbols}"
+
+ printf '%s(%s%s)' "${AGKOZAK_BRANCH_STATUS_SEPARATOR- }" "$branch" "$symbols"
+ fi
+}
+
+############################################################
+# Redraw the prompt when the vi mode changes. When the user
+# enters vi command mode, the % or # in the prompt changes
+# to a colon
+############################################################
+zle-keymap-select() {
+ [[ $KEYMAP == 'vicmd' ]] && psvar[4]='vicmd' || psvar[4]=''
+ zle reset-prompt
+ zle -R
+}
+
+############################################################
+# Redraw prompt when terminal size changes
+############################################################
+TRAPWINCH() {
+ zle && zle -R
+}
+
+############################################################
+# For legacy custom prompts: print a vi mode indicator
+############################################################
+_agkozak_vi_mode_indicator() {
+ case $KEYMAP in
+ vicmd) print -n ':' ;;
+ *) print -n '%#' ;;
+ esac
+}
+
+######################################################################
+# ASYNCHRONOUS FUNCTIONS
+######################################################################
+
+# Standarized $0 handling
+# (See https://github.com/zdharma/Zsh-100-Commits-Club/blob/master/Zsh-Plugin-Standard.adoc)
+0="${${ZERO:-${0:#$ZSH_ARGZERO}}:-${(%):-%N}}"
+typeset -g AGKOZAK_PROMPT_DIR="${0:A:h}"
+
+############################################################
+# If zsh-async has not already been loaded, try to load it
+#
+# Globals:
+# AGKOZAK_PROMPT_DEBUG
+# AGKOZAK_PROMPT_DIR
+############################################################
+_agkozak_load_async_lib() {
+ if ! whence -w async_init &> /dev/null; then # Don't load zsh-async twice
+ if (( AGKOZAK_PROMPT_DEBUG )); then
+ source "${AGKOZAK_PROMPT_DIR}/lib/async.zsh"
+ else
+ source "${AGKOZAK_PROMPT_DIR}/lib/async.zsh" &> /dev/null
+ fi
+ local success=$?
+ return $success
+ fi
+}
+
+############################################################
+# If SIGUSR1 is available and not already in use by ZSH, use
+# it; otherwise disable asynchronous mode
+############################################################
+_agkozak_has_usr1() {
+ if whence -w TRAPUSR1 &> /dev/null; then
+ _agkozak_debug_print 'TRAPUSR1 already defined.'
+ return 1
+ else
+ case $signals in # Array containing names of available signals
+ *USR1*) return 0 ;;
+ *)
+ _agkozak_debug_print 'SIGUSR1 not available.'
+ return 1
+ ;;
+ esac
+ fi
+}
+
+############################################################
+# If AGKOZAK_FORCE_ASYNC_METHOD is set to a valid value,
+# set AGKOZAK_ASYNC_METHOD to that; otherwise, determine
+# the optimal asynchronous method from the environment (usr1
+# for MSYS2/Cygwin, zsh-async for WSL, subst-async for
+# everything else), with fallbacks being available. Define
+# the necessary asynchronous functions (loading async.zsh
+# when necessary).
+#
+# Globals:
+# AGKOZAK_IS_WSL
+# AGKOZAK_ASYNC_METHOD
+# AGKOZAK_FORCE_ASYNC_METHOD
+# AGKOZAK_TRAPUSR1_FUNCTION
+############################################################
+_agkozak_async_init() {
+
+ # WSL should have BG_NICE disabled, since it does not have a Linux kernel
+ setopt LOCAL_OPTIONS EXTENDED_GLOB
+ if [[ -e /proc/version ]]; then
+ if [[ -n ${(M)${(f)"$(</proc/version)"}:#*Microsoft*} ]]; then
+ unsetopt BG_NICE
+ typeset -g AGKOZAK_IS_WSL=1 # For later reference
+ fi
+ fi
+
+ # If AGKOZAK_FORCE_ASYNC_METHOD is set, force the asynchronous method
+ [[ $AGKOZAK_FORCE_ASYNC_METHOD == 'zsh-async' ]] && _agkozak_load_async_lib
+ if [[ $AGKOZAK_FORCE_ASYNC_METHOD == (subst-async|zsh-async|usr1|none) ]]; then
+ typeset -g AGKOZAK_ASYNC_METHOD=$AGKOZAK_FORCE_ASYNC_METHOD
+
+ # Otherwise, first provide for certain quirky systems
+ else
+
+ if (( AGKOZAK_IS_WSL )) || [[ $OSTYPE == solaris* ]]; then
+ if [[ $ZSH_VERSION != '5.0.2' ]] &&_agkozak_load_async_lib; then
+ typeset -g AGKOZAK_ASYNC_METHOD='zsh-async'
+ elif _agkozak_has_usr1; then
+ typeset -g AGKOZAK_ASYNC_METHOD='usr1'
+ else
+ typeset -g AGKOZAK_ASYNC_METHOD='subst-async'
+ fi
+
+ # SIGUSR1 method is still much faster on MSYS2, Cygwin, and ZSH v5.0.2
+ elif [[ $ZSH_VERSION == '5.0.2' ]] || [[ $OSTYPE == (msys|cygwin) ]]; then
+ if _agkozak_has_usr1; then
+ typeset -g AGKOZAK_ASYNC_METHOD='usr1'
+ else
+ typeset -g AGKOZAK_ASYNC_METHOD='subst-async'
+ fi
+
+ # Asynchronous methods don't work in Emacs shell mode (but they do in term
+ # and ansi-term)
+ elif [[ $TERM == 'dumb' ]]; then
+ typeset -g AGKOZAK_ASYNC_METHOD='none'
+
+ # Otherwise use subst-async
+ else
+ typeset -g AGKOZAK_ASYNC_METHOD='subst-async'
+ fi
+ fi
+
+ ############################################################
+ # Process substitution async method
+ #
+ # Fork a background process to fetch the Git status and feed
+ # it asynchronously to a file descriptor. Install a callback
+ # handler to process input from the file descriptor.
+ #
+ # Globals:
+ # AGKOZAK_ASYNC_FD
+ # AGKOZAK_IS_WSL
+ ############################################################
+ _agkozak_subst_async() {
+ setopt LOCAL_OPTIONS NO_IGNORE_BRACES
+ typeset -g AGKOZAK_ASYNC_FD=13371
+
+ # Workaround for buggy behavior in MSYS2, Cygwin, and Solaris
+ if [[ $OSTYPE == (msys|cygwin|solaris*) ]]; then
+ exec {AGKOZAK_ASYNC_FD}< <(_agkozak_branch_status; command true)
+ # Prevent WSL from locking up when using X; also workaround for ZSH v5.0.2
+ elif (( AGKOZAK_IS_WSL )) && (( $+DISPLAY )) \
+ || [[ $ZSH_VERSION == '5.0.2' ]]; then
+ exec {AGKOZAK_ASYNC_FD}< <(_agkozak_branch_status)
+ command sleep 0.01
+ else
+ exec {AGKOZAK_ASYNC_FD}< <(_agkozak_branch_status)
+ fi
+
+ # Bug workaround; see http://www.zsh.org/mla/workers/2018/msg00966.html
+ command true
+
+ zle -F "$AGKOZAK_ASYNC_FD" _agkozak_zsh_subst_async_callback
+ }
+
+ ############################################################
+ # ZLE callback handler
+ #
+ # Read Git status from file descriptor and set psvar[3]
+ #
+ # Arguments:
+ # $1 File descriptor
+ ############################################################
+ _agkozak_zsh_subst_async_callback() {
+ setopt LOCAL_OPTIONS NO_IGNORE_BRACES
+
+ local FD="$1" response
+
+ # Read data from $FD descriptor
+ IFS='' builtin read -rs -d $'\0' -u "$FD" response
+
+ # Withdraw callback and close the file descriptor
+ zle -F ${FD}; exec {FD}<&-
+
+ # Make the changes visible
+ psvar[3]="$response"
+ zle && zle reset-prompt
+ }
+
+ case $AGKOZAK_ASYNC_METHOD in
+
+ zsh-async)
+
+ ############################################################
+ # Create zsh-async worker
+ ############################################################
+ _agkozak_zsh_async() {
+ async_start_worker agkozak_git_status_worker -n
+ async_register_callback agkozak_git_status_worker _agkozak_zsh_async_callback
+ async_job agkozak_git_status_worker _agkozak_branch_status
+ }
+
+ ############################################################
+ # Set RPROMPT and stop worker
+ ############################################################
+ _agkozak_zsh_async_callback() {
+ psvar[3]=$3
+ zle && zle reset-prompt
+ async_stop_worker agkozak_git_status_worker -n
+ }
+ ;;
+
+ usr1)
+
+ ############################################################
+ # Launch async workers to calculate Git status. TRAPUSR1
+ # actually displays the status; if some other script
+ # redefines TRAPUSR1, drop the prompt into synchronous mode.
+ #
+ # Globals:
+ # AGKOZAK_TRAPUSR1_FUNCTION
+ # AGKOZAK_USR1_ASYNC_WORKER
+ # AGKOZAK_ASYNC_METHOD
+ ############################################################
+ _agkozak_usr1_async() {
+ if [[ "$(builtin which TRAPUSR1)" = "$AGKOZAK_TRAPUSR1_FUNCTION" ]]; then
+ # Kill running child process if necessary
+ if (( AGKOZAK_USR1_ASYNC_WORKER )); then
+ kill -s HUP "$AGKOZAK_USR1_ASYNC_WORKER" &> /dev/null || :
+ fi
+
+ # Start background computation of Git status
+ _agkozak_usr1_async_worker &!
+ typeset -g AGKOZAK_USR1_ASYNC_WORKER=$!
+ else
+ _agkozak_debug_print 'TRAPUSR1 has been redefined. Switching to subst-async mode.'
+ typeset -g AGKOZAK_ASYNC_METHOD='subst-async'
+ psvar[3]="$(_agkozak_branch_status)"
+ fi
+ }
+
+ ############################################################
+ # Calculate Git status and store it in a temporary file;
+ # then kill own process, sending SIGUSR1
+ #
+ # Globals:
+ # AGKOZAK_PROMPT_DEBUG
+ ############################################################
+ _agkozak_usr1_async_worker() {
+ # Save Git branch status to temporary file
+ _agkozak_branch_status >| /tmp/agkozak_zsh_prompt_$$
+
+ # Signal parent process
+ if (( AGKOZAK_PROMPT_DEBUG )); then
+ kill -s USR1 $$
+ else
+ kill -s USR1 $$ &> /dev/null
+ fi
+ }
+
+ ############################################################
+ # On SIGUSR1, fetch Git status from temprary file and store
+ # it in psvar[3]. This function caches its own code in
+ # AGKOZAK_TRAPUSR1_FUNCTION so that it can tell if it has
+ # been redefined by another script.
+ #
+ # Globals:
+ # AGKOZAK_USR1_ASYNC_WORKER
+ # AGKOZAK_TRAPUSR1_FUNCTION
+ ############################################################
+ TRAPUSR1() {
+ # Set prompt from contents of temporary file
+ psvar[3]=$(print -n -- "$(< /tmp/agkozak_zsh_prompt_$$)")
+
+ # Reset asynchronous process number
+ typeset -g AGKOZAK_USR1_ASYNC_WORKER=0
+
+ # Redraw the prompt
+ zle && zle reset-prompt
+ }
+
+ typeset -g AGKOZAK_TRAPUSR1_FUNCTION="$(builtin which TRAPUSR1)"
+ ;;
+ esac
+}
+
+######################################################################
+# THE PROMPT
+######################################################################
+
+############################################################
+# Strip color codes from a prompt string
+#
+# Arguments:
+# $1 The prompt string
+############################################################
+_agkozak_strip_colors() {
+
+ local prompt=$1
+ local open_braces
+
+ while [[ -n $prompt ]]; do
+ case $prompt in
+ %F\{*|%K\{*)
+ (( open_braces++ ))
+ prompt=${prompt#%[FK]\{}
+ while (( open_braces )); do
+ case ${prompt:0:1} in
+ \{) (( open_braces++ )) ;;
+ \}) (( open_braces-- )) ;;
+ esac
+ prompt=${prompt#?}
+ done
+ ;;
+ %f*|%k*) prompt=${prompt#%[fk]} ;;
+ *)
+ print -n -- "${prompt:0:1}"
+ prompt=${prompt#?}
+ ;;
+ esac
+ done
+}
+
+############################################################
+# Runs right before each prompt is displayed; hooks into
+# precmd
+#
+# 1) Redisplays path ($psvar[2]) whenever the value of
+# AGKOZAK_PROMPT_DIRTRIM or AGKOZAK_NAMED_DIRS changes
+# 2) If AGKOZAK_MULTILINE is changed to 0, set
+# AGKOZAK_LEFT_PROMPT_ONLY=0
+# 3) If AGKOZAK_LEFT_PROMPT_ONLY is changed, updated both
+# prompt strings
+# 4) Resets Git status and vi mode display
+# 5) Begins to calculate Git status
+# 6) Sets AGKOZAK_PROMPT_WHITESPACE based on value of
+# AGKOZAK_MULTILINE
+# 7) Optionally display a blank line (AGKOZAK_BLANK_LINES),
+# while avoiding a blank line when the shell is first
+# loaded
+# 8) If custom prompts are defined, update the prompt
+# strings
+#
+# TODO: Consider making AGKOZAK_PROMPT_WHITESPACE a psvar
+#
+# Globals:
+# AGKOZAK_PROMPT_DIRTRIM
+# AGKOZAK_OLD_PROMPT_DIRTRIM
+# AGKOZAK_NAMED_DIRS
+# AGKOZAK_OLD_NAMED_DIRS
+# AGKOZAK_MULTILINE
+# AGKOZAK_OLD_MULTILINE
+# AGKOZAK_LEFT_PROMPT_ONLY
+# AGKOZAK_OLD_LEFT_PROMPT_ONLY
+# AGKOZAK_ASYNC_METHOD
+# AGKOZAK_PROMPT_WHITESPACE
+# AGKOZAK_BLANK_LINES
+# AGKOZAK_FIRST_PROMPT_PRINTED
+# AGKOZAK_CUSTOM_PROMPT
+# AGKOZAK_CURRENT_CUSTOM_PROMPT
+# AGKOZAK_CUSTOM_RPROMPT
+# AGKOZAK_CURRENT_CUSTOM_RPROMPT
+############################################################
+_agkozak_precmd() {
+ # Update displayed directory when AGKOZAK_PROMPT_DIRTRIM or AGKOZAK_NAMED_DIRS
+ # changes or when first sourcing this script
+ if (( AGKOZAK_PROMPT_DIRTRIM != AGKOZAK_OLD_PROMPT_DIRTRIM )) \
+ || (( AGKOZAK_NAMED_DIRS != AGKOZAK_OLD_NAMED_DIRS )) \
+ || (( ! $+psvar[2] )); then
+ _agkozak_prompt_dirtrim -v $AGKOZAK_PROMPT_DIRTRIM
+ typeset -g AGKOZAK_OLD_PROMPT_DIRTRIM=$AGKOZAK_PROMPT_DIRTRIM
+ typeset -g AGKOZAK_OLD_NAMED_DIRS=$AGKOZAK_NAMED_DIRS
+ fi
+
+ if (( AGKOZAK_MULTILINE != AGKOZAK_OLD_MULTILINE )); then
+ (( AGKOZAK_MULTILINE == 0 )) && AGKOZAK_LEFT_PROMPT_ONLY=0
+ typeset -g AGKOZAK_OLD_MULTILINE=$AGKOZAK_MULTILINE
+ fi
+
+ if (( AGKOZAK_LEFT_PROMPT_ONLY != AGKOZAK_OLD_LEFT_PROMPT_ONLY )); then
+ unset AGKOZAK_CUSTOM_PROMPT AGKOZAK_CUSTOM_RPROMPT
+ typeset -g AGKOZAK_OLD_LEFT_PROMPT_ONLY=$AGKOZAK_LEFT_PROMPT_ONLY
+ _agkozak_prompt_string
+ fi
+
+ psvar[3]=''
+ psvar[4]=''
+
+ case $AGKOZAK_ASYNC_METHOD in
+ 'subst-async') _agkozak_subst_async ;;
+ 'zsh-async') _agkozak_zsh_async ;;
+ 'usr1') _agkozak_usr1_async ;;
+ *) psvar[3]="$(_agkozak_branch_status)" ;;
+ esac
+
+ if (( AGKOZAK_MULTILINE == 0 )) && (( ! AGKOZAK_LEFT_PROMPT_ONLY )) \
+ && [[ -z $INSIDE_EMACS ]]; then
+ typeset -g AGKOZAK_PROMPT_WHITESPACE=' '
+ else
+ typeset -g AGKOZAK_PROMPT_WHITESPACE=$'\n'
+ fi
+
+ if (( AGKOZAK_BLANK_LINES )); then
+ if (( AGKOZAK_FIRST_PROMPT_PRINTED )); then
+ print
+ fi
+ typeset -g AGKOZAK_FIRST_PROMPT_PRINTED=1
+ fi
+
+ # If AGKOZAK_CUSTOM_PROMPT or AGKOZAK_CUSTOM_RPROMPT changes, the
+ # corresponding prompt is updated
+
+ if [[ ${AGKOZAK_CUSTOM_PROMPT} != "${AGKOZAK_CURRENT_CUSTOM_PROMPT}" ]]; then
+ typeset -g AGKOZAK_CURRENT_CUSTOM_PROMPT=${AGKOZAK_CUSTOM_PROMPT}
+ PROMPT=${AGKOZAK_CUSTOM_PROMPT}
+ if ! _agkozak_has_colors; then
+ PROMPT=$(_agkozak_strip_colors "${PROMPT}")
+ fi
+ fi
+
+ if [[ ${AGKOZAK_CUSTOM_RPROMPT} != "${AGKOZAK_CURRENT_CUSTOM_RPROMPT}" ]]; then
+ typeset -g AGKOZAK_CURRENT_CUSTOM_RPROMPT=${AGKOZAK_CUSTOM_RPROMPT}
+ RPROMPT=${AGKOZAK_CUSTOM_RPROMPT}
+ if ! _agkozak_has_colors; then
+ RPROMPT=$(_agkozak_strip_colors "${RPROMPT}")
+ fi
+ fi
+}
+
+############################################################
+# Set the prompt strings
+#
+# Globals:
+# AGKOZAK_CUSTOM_PROMPT
+# AGKOZAK_COLORS_EXIT_STATUS
+# AGKOZAK_COLORS_USER_HOST
+# AGKOZAK_COLORS_PATH
+# AGKOZAK_PROMPT_WHITESPACE
+# AGKOZAK_COLORS_PROMPT_CHAR
+# AGKOZAK_PROMPT_CHAR
+# AGKOZAK_CURRENT_CUSTOM_PROMPT
+# AGKOZAK_CUSTOM_RPROMPT
+# AGKOZAK_COLORS_BRANCH_STATUS
+# AGKOZAK_CURRENT_CUSTOM_RPROMPT
+############################################################
+_agkozak_prompt_string () {
+ if (( $+AGKOZAK_CUSTOM_PROMPT )); then
+ PROMPT=${AGKOZAK_CUSTOM_PROMPT}
+ else
+ # The color left prompt
+ PROMPT='%(?..%B%F{${AGKOZAK_COLORS_EXIT_STATUS}}(%?%)%f%b )'
+ PROMPT+='%(!.%S%B.%B%F{${AGKOZAK_COLORS_USER_HOST}})%n%1v%(!.%b%s.%f%b) '
+ PROMPT+='%B%F{${AGKOZAK_COLORS_PATH}}%2v%f%b'
+ if (( AGKOZAK_LEFT_PROMPT_ONLY )); then
+ PROMPT+='%(3V.%F{${AGKOZAK_COLORS_BRANCH_STATUS}}%3v%f.)'
+ fi
+ PROMPT+='${AGKOZAK_PROMPT_WHITESPACE}'
+ PROMPT+='${AGKOZAK_COLORS_PROMPT_CHAR:+%F{${AGKOZAK_COLORS_PROMPT_CHAR}\}}'
+ PROMPT+='%(4V.${AGKOZAK_PROMPT_CHAR[3]:-:}.%(!.${AGKOZAK_PROMPT_CHAR[2]:-%#}.${AGKOZAK_PROMPT_CHAR[1]:-%#}))'
+ PROMPT+='${AGKOZAK_COLORS_PROMPT_CHAR:+%f} '
+
+ typeset -g AGKOZAK_CUSTOM_PROMPT=${PROMPT}
+ typeset -g AGKOZAK_CURRENT_CUSTOM_PROMPT=${AGKOZAK_CUSTOM_PROMPT}
+ fi
+
+ if (( $+AGKOZAK_CUSTOM_RPROMPT )); then
+ RPROMPT=${AGKOZAK_CUSTOM_RPROMPT}
+ else
+ # The color right prompt
+ if (( ! AGKOZAK_LEFT_PROMPT_ONLY )); then
+ typeset -g RPROMPT='%(3V.%F{${AGKOZAK_COLORS_BRANCH_STATUS}}%3v%f.)'
+ else
+ typeset -g RPROMPT=''
+ fi
+
+ typeset -g AGKOZAK_CUSTOM_RPROMPT=${RPROMPT}
+ typeset -g AGKOZAK_CURRENT_CUSTOM_RPROMPT=${RPROMPT}
+ fi
+
+ if ! _agkozak_has_colors; then
+ PROMPT=$(_agkozak_strip_colors "$PROMPT")
+ RPROMPT=$(_agkozak_strip_colors "$RPROMPT")
+ fi
+}
+
+############################################################
+# Prompt setup
+#
+# Globals:
+# AGKOZAK_ASYNC_METHOD
+# AGKOZAK_USR1_ASYNC_WORKER
+# AGKOZAK_PROMPT_DIRTRIM
+############################################################
+() {
+
+ _agkozak_async_init
+
+ case $AGKOZAK_ASYNC_METHOD in
+ 'subst-async') ;;
+ 'zsh-async') async_init ;;
+ 'usr1') typeset -g AGKOZAK_USR1_ASYNC_WORKER=0 ;;
+ esac
+
+ zle -N zle-keymap-select
+
+ # Don't use ZSH hooks in Emacs classic shell
+ if (( $+INSIDE_EMACS )) && [[ $TERM == 'dumb' ]]; then
+ :
+ else
+ autoload -Uz add-zsh-hook
+ add-zsh-hook precmd _agkozak_precmd
+
+ ############################################################
+ # Update the displayed directory when the PWD changes
+ ############################################################
+ _agkozak_chpwd() {
+ _agkozak_prompt_dirtrim -v $AGKOZAK_PROMPT_DIRTRIM
+ }
+
+ add-zsh-hook chpwd _agkozak_chpwd
+ fi
+
+ # Only display the HOSTNAME for an SSH connection or for a superuser
+ if _agkozak_is_ssh || (( EUID == 0 )); then
+ psvar[1]="@${HOST%%.*}"
+ else
+ psvar[1]=''
+ fi
+
+ # The DragonFly BSD console and Emacs shell can't handle bracketed paste.
+ # Avoid the ugly ^[[?2004 control sequence.
+ if [[ $TERM == 'cons25' ]] || [[ $TERM == 'dumb' ]]; then
+ unset zle_bracketed_paste
+ fi
+
+ # The Emacs shell has only limited support for some ZSH features, so use a
+ # more limited prompt.
+ if [[ $TERM == 'dumb' ]]; then
+ PROMPT='%(?..(%?%) )'
+ PROMPT+='%n%1v '
+ PROMPT+='$(_agkozak_prompt_dirtrim "$AGKOZAK_PROMPT_DIRTRIM")'
+ PROMPT+='$(_agkozak_branch_status) '
+ PROMPT+='%# '
+ else
+ # Avoid continuation lines in Emacs term and ansi-term
+ (( $+INSIDE_EMACS )) && ZLE_RPROMPT_INDENT=3
+
+ # When VSCode is using the DOM renderer, the right prompt overflows off the
+ # side of the screen
+ (( $+VSCODE_PID )) && ZLE_RPROMPT_INDENT=6
+
+ _agkozak_prompt_string
+
+ fi
+
+ _agkozak_debug_print "Using async method: $AGKOZAK_ASYNC_METHOD"
+}
+
+# Clean up environment
+unfunction _agkozak_load_async_lib _agkozak_has_usr1 _agkozak_is_ssh \
+ _agkozak_async_init
+
+# vim: ts=2:et:sts=2:sw=2:ROMPT='%~%<< $(git_prompt_info)${PR_BOLD_WHITE}>%{${reset_color}%} '
diff --git a/common/config/zsh/user/prompt_simple.zsh b/common/config/zsh/user/prompt_simple.zsh
new file mode 100644
index 0000000..0bbad44
--- /dev/null
+++ b/common/config/zsh/user/prompt_simple.zsh
@@ -0,0 +1,227 @@
+# vim:ft=zsh ts=2 sw=2 sts=2
+#
+### Segment drawing
+# A few utility functions to make it easy and re-usable to draw segmented prompts
+CURRENT_BG='NONE'
+
+case ${SOLARIZED_THEME:-dark} in
+ light) CURRENT_FG='white';;
+ *) CURRENT_FG='black';;
+esac
+
+# Segments
+() {
+ local LC_ALL="" LC_CTYPE="en_US.UTF-8"
+ SEGMENT_SEPARATOR=
+}
+
+# Begin a segment
+# Takes two arguments, background and foreground. Both can be omitted,
+# rendering default background/foreground.
+prompt_segment() {
+ local bg fg
+ [[ -n $2 ]] && fg="$FG[254]" || fg="%f"
+ if [[ $CURRENT_BG != 'NONE' && $1 != $CURRENT_BG ]]; then
+ echo -n " %{$bg%F{$CURRENT_BG}%}$SEGMENT_SEPARATOR%{$fg%} "
+ else
+ echo -n "%{$bg%}%{$fg%}"
+ fi
+ CURRENT_BG=$1
+ [[ -n $3 ]] && echo -n $3
+}
+
+# End the prompt, closing any open segments
+prompt_end() {
+ if [[ -n $CURRENT_BG ]]; then
+ echo -n " %{%k%F{$CURRENT_BG}%}$SEGMENT_SEPARATOR"
+ else
+ echo -n "%{%k%}"
+ fi
+ echo -n "%{%f%}"
+ CURRENT_BG=''
+}
+
+### Prompt components
+# Each component will draw itself, and hide itself if no information needs to be shown
+
+# Context: user@hostname (who am I and where am I)
+prompt_context() { }
+
+parse_git_dirty() {
+ local -a git_status
+ git_status=($(git status --porcelain 2>/dev/null))
+ if [[ ${#git_status[@]} -gt 0 ]]; then
+ echo "±"
+ fi
+}
+
+# Git: branch/detached head, dirty status
+prompt_git() {
+ (( $+commands[git] )) || return
+ if [[ "$(git config --get oh-my-zsh.hide-status 2>/dev/null)" = 1 ]]; then
+ return
+ fi
+ local PL_BRANCH_CHAR
+ () {
+ local LC_ALL="" LC_CTYPE="en_US.UTF-8"
+ PL_BRANCH_CHAR=$'' # 
+ }
+ local ref dirty mode repo_path
+
+ if [[ "$(git rev-parse --is-inside-work-tree 2>/dev/null)" = "true" ]]; then
+ repo_path=$(git rev-parse --git-dir 2>/dev/null)
+ dirty=$(parse_git_dirty)
+ ref=$(git symbolic-ref HEAD 2> /dev/null) || ref="➦ $(git rev-parse --short HEAD 2> /dev/null)"
+ if [[ -n $dirty ]]; then
+ prompt_segment yellow black
+ PL_BRANCH_CHAR=%{%F{yellow}%}''
+ else
+ prompt_segment green $CURRENT_FG
+ fi
+
+ if [[ -e "${repo_path}/BISECT_LOG" ]]; then
+ mode=" <B>"
+ elif [[ -e "${repo_path}/MERGE_HEAD" ]]; then
+ mode=" >M<"
+ elif [[ -e "${repo_path}/rebase" || -e "${repo_path}/rebase-apply" || -e "${repo_path}/rebase-merge" || -e "${repo_path}/../.dotest" ]]; then
+ mode=" >R>"
+ fi
+
+ setopt promptsubst
+ autoload -Uz vcs_info
+
+ zstyle ':vcs_info:*' enable git
+ zstyle ':vcs_info:*' get-revision true
+ zstyle ':vcs_info:*' check-for-changes true
+ zstyle ':vcs_info:*' stagedstr '✚'
+ zstyle ':vcs_info:*' unstagedstr '± '
+ zstyle ':vcs_info:*' formats ' %u%c'
+ zstyle ':vcs_info:*' actionformats ' %u%c'
+ vcs_info
+ echo -n "${ref/refs\/heads\//$PL_BRANCH_CHAR }${vcs_info_msg_0_%% }${mode}"
+ fi
+}
+
+prompt_bzr() {
+ (( $+commands[bzr] )) || return
+
+ # Test if bzr repository in directory hierarchy
+ local dir="$PWD"
+ while [[ ! -d "$dir/.bzr" ]]; do
+ [[ "$dir" = "/" ]] && return
+ dir="${dir:h}"
+ done
+
+ local bzr_status status_mod status_all revision
+ if bzr_status=$(bzr status 2>&1); then
+ status_mod=$(echo -n "$bzr_status" | head -n1 | grep "modified" | wc -m)
+ status_all=$(echo -n "$bzr_status" | head -n1 | wc -m)
+ revision=$(bzr log -r-1 --log-format line | cut -d: -f1)
+ if [[ $status_mod -gt 0 ]] ; then
+ prompt_segment yellow black "bzr@$revision ✚"
+ else
+ if [[ $status_all -gt 0 ]] ; then
+ prompt_segment yellow black "bzr@$revision"
+ else
+ prompt_segment green black "bzr@$revision"
+ fi
+ fi
+ fi
+}
+
+prompt_hg() {
+ (( $+commands[hg] )) || return
+ local rev st branch
+ if $(hg id >/dev/null 2>&1); then
+ if $(hg prompt >/dev/null 2>&1); then
+ if [[ $(hg prompt "{status|unknown}") = "?" ]]; then
+ # if files are not added
+ prompt_segment red white
+ st='±'
+ elif [[ -n $(hg prompt "{status|modified}") ]]; then
+ # if any modification
+ prompt_segment yellow black
+ st='±'
+ else
+ # if working copy is clean
+ prompt_segment green $CURRENT_FG
+ fi
+ echo -n $(hg prompt "☿ {rev}@{branch}") $st
+ else
+ st=""
+ rev=$(hg id -n 2>/dev/null | sed 's/[^-0-9]//g')
+ branch=$(hg id -b 2>/dev/null)
+ if `hg st | grep -q "^\?"`; then
+ prompt_segment red black
+ st='±'
+ elif `hg st | grep -q "^[MA]"`; then
+ prompt_segment yellow black
+ st='±'
+ else
+ prompt_segment green $CURRENT_FG
+ fi
+ echo -n "☿ $rev@$branch" $st
+ fi
+ fi
+}
+
+# Change prompt for HOME dir
+prompt_dir () {
+ if [[ "$PWD" == "$HOME" ]]; then
+ prompt_segment blue $CURRENT_FG ''
+ else
+ prompt_segment blue CURRENT_FG '%2~'
+ fi
+}
+
+# Virtualenv: current working virtualenv
+prompt_virtualenv() {
+ local virtualenv_path="$VIRTUAL_ENV"
+ if [[ -n $virtualenv_path && -n $VIRTUAL_ENV_DISABLE_PROMPT ]]; then
+ prompt_segment blue black "(`basename $virtualenv_path`)"
+ fi
+}
+
+# Status:
+# - was there an error
+# - am I root
+# - are there background jobs?
+prompt_status() {
+ local -a symbols
+
+ [[ $RETVAL -ne 0 ]] && symbols+=" %{%F{red}%}"
+ [[ $UID -eq 0 ]] && symbols+="%{%F{yellow}%}⚡"
+ [[ $(jobs -l | wc -l) -gt 0 ]] && symbols+="%{%F{cyan}%}⚙"
+
+ [[ -n "$symbols" ]] && prompt_segment black default "$symbols"
+}
+
+#AWS Profile:
+# - display current AWS_PROFILE name
+# - displays yellow on red if profile name contains 'production' or
+# ends in '-prod'
+# - displays black on green otherwise
+prompt_aws() {
+ [[ -z "$AWS_PROFILE" || "$SHOW_AWS_PROMPT" = false ]] && return
+ case "$AWS_PROFILE" in
+ *-prod|*production*) prompt_segment red yellow "AWS: $AWS_PROFILE" ;;
+ *) prompt_segment green black "AWS: $AWS_PROFILE" ;;
+ esac
+}
+
+## Main prompt
+build_prompt() {
+ RETVAL=$?
+ prompt_status
+ prompt_virtualenv
+ prompt_aws
+ prompt_context
+ prompt_dir
+ prompt_git
+ prompt_bzr
+ prompt_hg
+ prompt_end
+}
+
+PROMPT='%{%F{blue}%}  %{%f%b%k%}$(build_prompt) '
+bindkey -M vicmd '\e[C' vi-forward-char # ESC + right arrow