# # ZSH Interactive Shell Setup # # There's a few scenarios where we should bail from interactive shell setup, as # in our full interactive shell setup we have tools like `mise` and others that # continuously update `PATH` among other things. So scenarios that need a static # environment don't play nice with this. # # The following scenarios are the ones we bail from: # # - VSCode's environment resolution process, as all it cares for it to load PATH # and other environment variables. We specifically have tools like `mise` that # continuously update PATH, # - Anything that sets the `TERM` environment variable to `dumb`. This includes # Cursor's agent setup, which is used when the agent runs terminal commands. # Though it does also set a proper `TERM` value sometimes, hence the check for # `CURSOR_AGENT` as well. # - Claude Code's shell sessions, identified by `CLAUDECODE=1`. # if [[ -n "$VSCODE_RESOLVING_ENVIRONMENT" ]] || [[ "$TERM" == "dumb" ]] || [[ "$CURSOR_AGENT" == "1" ]] || [[ "$CLAUDECODE" == "1" ]]; then return fi # In our `zshenv` file we have on macOS disabled loading ZSH start-up files from # `/etc` to avoid `/etc/zprofile` messing up our carefully constructed `PATH`. # So we need to manually load the other files we care about. if [[ "$OSTYPE" == "darwin"* ]] && [ -f "/etc/zshrc" ]; then source "/etc/zshrc" fi # ============================================================================== # Helper Functions # ============================================================================== source "$DOTZSH/zshrc.funcs.zsh" # ============================================================================== # Zinit # ============================================================================== declare -A ZINIT ZINIT[HOME_DIR]="$HOME/.local/zsh/zinit" ZINIT[BIN_DIR]="${ZINIT[HOME_DIR]}/bin" # Load zinit module if it exists. For more info, run: zinit module help if [ -f "${ZINIT[HOME_DIR]}/module/Src/zdharma_continuum/zinit.so" ]; then module_path+=("${ZINIT[HOME_DIR]}/module/Src") zmodload zdharma_continuum/zinit fi # Ask to clone Zinit if it's not already available on disk. [ ! -d "${ZINIT[BIN_DIR]}" ] && read -q "REPLY?Zinit not installed, clone to ${ZINIT[BIN_DIR]}? [y/N]:" && echo && git clone --depth=1 "https://github.com/zdharma-continuum/zinit.git" "${ZINIT[BIN_DIR]}" # Remove any stale Zinit plugin paths before loading to avoid issues. path=("${(@)path:#${ZINIT[HOME_DIR]}/plugins/*}") # Load Zinit source "${ZINIT[BIN_DIR]}/zinit.zsh" # Add generic cross platform `clipcopy` and `clippaste` commands to copy and # paste from the system clipboard. zinit for @OMZ::lib/clipboard.zsh # ============================================================================== # Environment # ============================================================================== # Ensure mise shims directory is in PATH so that installed tools are immediately # available for use within shell initialization. We activate mise normally right # at end which replaces the shims in PATH with a shell hook that updates PATH as # needed. path_prepend "$MISE_HOME/shims" # If available, make sure to load direnv shell hook before mise. if command-exists direnv; then cached-eval "$(mise-which direnv)" direnv hook zsh fi # ============================================================================== # History # ============================================================================== # Set various sane defaults for ZSH history management. zinit for @OMZ::lib/history.zsh # Map history search to and . # bindkey '^p' history-beginning-search-backward # bindkey '^n' history-beginning-search-forward ## History file configuration HISTFILE="$HOME/.zsh_history" HISTSIZE=100000 SAVEHIST=50000 ## History command configuration setopt append_history # Append history to `HISTFILE`. setopt extended_history # Record timestamp of command in `HISTFILE`. setopt hist_expire_dups_first # Delete duplicates first when `HISTFILE` size exceeds `HISTSIZE`. setopt hist_find_no_dups # Do not display a duplicate history entry. setopt hist_ignore_dups # Ignore duplicated commands history list. setopt hist_ignore_space # Ignore commands that start with space. setopt hist_reduce_blanks # Remove superfluous blanks before adding to history. setopt hist_verify # Show command with history expansion to user before running it. setopt inc_append_history_time # Add timestamp to `HISTFILE` in order of execution. setopt share_history # Share command history data. # ============================================================================== # Completion # ============================================================================== if [ -d "$ZSH_COMPLETIONS" ]; then fpath=("$ZSH_COMPLETIONS" $fpath); fi if [ -d "$DOTZSH_SITEFUNS" ]; then fpath=("$DOTZSH_SITEFUNS" $fpath); fi if [ -d "$BREW_SITEFUNS" ]; then fpath=("$BREW_SITEFUNS" $fpath); fi # Enable interactive selection of completions. zinit for @OMZ::lib/completion.zsh # Install fzf-tab if fzf is available. if command-exists fzf; then # TODO: Switch back to upstream Aloxaf/fzf-tab when this PR is merged: # - https://github.com/Aloxaf/fzf-tab/pull/445 zinit light-mode wait lucid atclone'git checkout fit-preview' \ for @jimeh/fzf-tab zstyle ':fzf-tab:sources' config-directory "$DOTZSH/fzf-tab/sources" zinit light-mode lucid for @Freed-Wu/fzf-tab-source # Disable some overly aggressive completions from fzf-tab-source. zstyle -d ':fzf-tab:complete:*' fzf-preview fi zinit light-mode wait lucid blockf for @zsh-users/zsh-completions zinit light-mode wait lucid atload"!_zsh_autosuggest_start" \ for @zsh-users/zsh-autosuggestions # Map ctrl+x h to show completion context info. bindkey '^Xh' _complete_help # Group completions by type under group headings zstyle ':completion:*' format '%d' zstyle ':completion:*' group-name '' zstyle ':completion:*:descriptions' format '%d' zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS} zstyle ':completion:*' list-grouped true zstyle ':autocomplete:*' groups 'always' # Case-insensitive completion. zstyle ':completion:*' matcher-list 'm:{[:lower:][:upper:]-_}={[:upper:][:lower:]_-}' 'r:|=*' 'l:|=* r:|=*' # git zstyle ':completion:*:*:git:*:branches' group-name 'local branches' zstyle ':completion:*:*:git:*:remote-branches' group-name 'remote branches' zstyle ':completion:*:*:git:*:tags' group-name 'tags' zstyle ':completion:*:git-checkout:*' sort false # Improve selection of Makefile completions - from: # https://github.com/zsh-users/zsh-completions/issues/541#issuecomment-384223016 zstyle ':completion:*:make:*:targets' call-command true autoload -Uz compinit compinit # Setup fzf related stuff if it is available. if command-exists fzf; then export FZF_DEFAULT_OPTS=" --bind=ctrl-k:kill-line --bind=ctrl-v:half-page-down --bind=alt-v:half-page-up --tabstop=4 --highlight-line" export FZF_CTRL_T_OPTS=" --tmux=75% --walker-skip=.git,node_modules,.terraform,target --bind='ctrl-/:change-preview-window(down|hidden|)'" # TODO: replace pbcopy with something that's cross-platform. export FZF_CTRL_R_OPTS=" --tmux=75% --border=rounded --preview='echo {}' --preview-window=down:3:hidden:wrap --bind='ctrl-/:toggle-preview' --bind='ctrl-y:execute-silent(echo -n {2..} | pbcopy)+abort'" export FZF_ALT_C_OPTS=" --tmux=75% --walker-skip=.git,node_modules,.terraform,target --preview='tree -C {}'" cached-eval "$(mise-which fzf)" fzf --zsh zstyle ':completion:*' menu no zstyle ':completion:*' special-dirs true zstyle ':fzf-tab:*' fzf-bindings \ 'ctrl-v:half-page-down' \ 'alt-v:half-page-up' \ 'ctrl-k:kill-line' zstyle ':fzf-tab:*' fzf-flags \ '--highlight-line' \ '--tabstop=4' zstyle ':fzf-tab:*' prefix '' zstyle ':fzf-tab:*' switch-group '<' '>' # Use fzf-tab's tmux pop-up for tab completion. zstyle ':fzf-tab:*' fzf-command ftb-tmux-popup zstyle ':fzf-tab:*' popup-min-size 30 10 zstyle ':fzf-tab:*' popup-pad 0 0 zstyle ':fzf-tab:*' popup-fit-preview yes if command-exists eza; then fzf_dir_preview='eza -1 --color=always --icons $realpath' zstyle ':fzf-tab:complete:eza:*' fzf-preview "$fzf_dir_preview" else fzf_dir_preview='ls -1 --color=always $realpath' fi zstyle ':fzf-tab:complete:cd:*' fzf-preview "$fzf_dir_preview" zstyle ':fzf-tab:complete:ls:*' fzf-preview "$fzf_dir_preview" if command-exists bat; then fzf_bat_preview='bat --color=always -n -r :500' FZF_CTRL_T_OPTS="$FZF_CTRL_T_OPTS --preview '$fzf_bat_preview {}'" zstyle ':fzf-tab:complete:bat:*' fzf-preview "$fzf_bat_preview \$realpath" zstyle ':fzf-tab:complete:cat:*' fzf-preview "$fzf_bat_preview \$realpath" else FZF_CTRL_T_OPTS="$FZF_CTRL_T_OPTS --preview 'less {}'" fi fi # ============================================================================== # Visuals # ============================================================================== zinit light-mode wait lucid \ atinit"ZINIT[COMPINIT_OPTS]=-C; zicompinit; zicdreplay" \ for @zdharma-continuum/fast-syntax-highlighting # ============================================================================== # Command line keybindings # ============================================================================== autoload -z edit-command-line zle -N edit-command-line bindkey "^X^E" edit-command-line # Support for alt+ in some terminals. bindkey "\e[3;3~" kill-word # ============================================================================== # Environment and Tool Managers # ============================================================================== MISE_HOME="$HOME/.local/share/mise" export MISE_INSTALL_PATH="$HOME/.local/bin/mise" if ! command-exists mise; then read -qr 'REPLY?mise is not installed,' \ 'install with "curl https://mise.run | sh"? [y/N]:' && echo && curl https://mise.run | sh fi if command-exists mise; then # Remove any stale mise install paths before activation to ensure it correctly # activates. This ensures any tooling that starts shells with an existing set # of cached env vars will not cause issues. I'm looking at you, direnv for # VSCode. path=("${(@)path:#${MISE_HOME}/installs/*}") # Activate mise. We cannot use cached-eval here as the activation script # dynamically adjusts how it modifies PATH on each invocation. eval "$("$MISE_INSTALL_PATH" activate zsh)" setup-completions mise "$MISE_INSTALL_PATH" mise completions zsh alias mi="mise" fi # ============================================================================== # Prompt # ============================================================================== if ! command-exists starship && [ -f "$MISE_INSTALL_PATH" ]; then read -qr 'REPLY?starship is not installed,' \ 'install with "mise use -g starship"? [y/N]:' && echo && "$MISE_INSTALL_PATH" use -g starship fi if command-exists starship; then cached-eval "$(mise-which starship)" starship init zsh --print-full-init setup-completions starship "$(mise-which starship)" starship completions zsh else echo "WARN: starship not found, skipping prompt setup" >&2 echo " install with: mise use -g starship" >&2 echo "" >&2 fi # ============================================================================== # Private Dotfiles # ============================================================================== if [ -f "$DOTPFILES/zshrc" ]; then source "$DOTPFILES/zshrc" fi # ============================================================================== # Tool specific setup # ============================================================================== # Aliases source "$DOTZSH/aliases.zsh" # OS specific if [[ "$OSTYPE" == "darwin"* ]]; then source "$DOTZSH/macos.zsh"; fi if [[ "$OSTYPE" == "linux"* ]]; then source "$DOTZSH/linux.zsh"; fi # Utils source "$DOTZSH/1password.zsh" source "$DOTZSH/ansi.zsh" source "$DOTZSH/atuin.zsh" source "$DOTZSH/copilot.zsh" source "$DOTZSH/emacs.zsh" source "$DOTZSH/cursor.zsh" source "$DOTZSH/less.zsh" source "$DOTZSH/mise.zsh" source "$DOTZSH/neovim.zsh" source "$DOTZSH/nix.zsh" source "$DOTZSH/restish.zsh" source "$DOTZSH/tldr.zsh" source "$DOTZSH/tmux.zsh" source "$DOTZSH/iterm2.zsh" source "$DOTZSH/vscode.zsh" source "$DOTZSH/zoxide.zsh" # Development source "$DOTZSH/containers.zsh" source "$DOTZSH/git.zsh" source "$DOTZSH/golang.zsh" source "$DOTZSH/google-cloud.zsh" source "$DOTZSH/kubernetes.zsh" source "$DOTZSH/nodejs.zsh" source "$DOTZSH/python.zsh" source "$DOTZSH/ruby.zsh" source "$DOTZSH/rust.zsh" source "$DOTZSH/scaleway.zsh" # ============================================================================== # Basic Z-Shell settings # ============================================================================== # Disable auto-title. DISABLE_AUTO_TITLE="true" # Disable shared history. unsetopt share_history # Disable attempted correction of commands (is wrong 98% of the time). unsetopt correctall # ============================================================================== # Local Overrides # ============================================================================== if [ -f "$HOME/.zshrc.local" ]; then source "$HOME/.zshrc.local" fi autoload -U +X bashcompinit && bashcompinit