#!/bin/zsh # dotfiles: zshrc # Dan R. K. Ports # $Id$ emulate zsh # # Discover architecture and hostname # sys=${(L)$(uname)} name=${HOST%%.*} # # Bindings # bindkey -e # Default to Emacs-like bindings, even # when the EDITOR is vi # # Line editing # WORDCHARS=${WORDCHARS//[\/.&]} # Be more allowing with word skipping # Auto-quote shell metacharacters in URLs as they're typed (so # `curl https://x/?a=1&b=2` doesn't background or glob). autoload -Uz url-quote-magic zle -N self-insert url-quote-magic # Disable other self-insert widgets (e.g. url-quote-magic) during # bracketed paste, so pasted text is inserted verbatim. autoload -Uz bracketed-paste-magic zle -N bracketed-paste bracketed-paste-magic # # Paths # typeset -U path manpath # Uniquify entries in paths #export -TU LD_LIBRARY_PATH ld_library_path # Make a new path array/string # Feed the path arrays. Include just about everything anyone could # ever possibly want, since many systems seem to be missing important # things like /sbin. path=(~/bin ~/.local/bin ~/$sys/bin ~/scripts /opt/local/bin /opt/local/sbin /usr/local/bin /usr/local/sbin /usr/bin /usr/sbin /bin /sbin $path) manpath=(~/man ~/.local/share/man ~/$sys/man /opt/local/man /opt/local/share/man /usr/local/man /usr/share/man /usr/man $manpath ) #ld_library_path=(~/lib ~/$sys/lib /opt/local/lib # /usr/local/lib /usr/lib /lib # $ld_library_path) fpath=(/opt/local/share/zsh/site-functions /opt/local/share/zsh/$ZSH_VERSION/functions $fpath) export TERMINFO_DIRS=$TERMINFO_DIRS:$HOME/.local/share/terminfo # # Set up precmd/prexec hooks # autoload -Uz add-zsh-hook # Grab results for any precmds that want it: must be first precmd! precmd_grabresult() { result=$? } add-zsh-hook precmd precmd_grabresult # # Customize prompt # setopt PROMPT_SUBST # Evaluate shell substitutions in # prompt string # Truly horrifying prompt string case "$TERM" in "dumb") unsetopt zle PS1="> " ;; *) PS1='%B%(!.%F{red}.)${HAVE_KRBTICKETS:+%U}%n%f${HAVE_KRBTICKETS:+%u}@%m%b:%~${vcs_info_msg_0_} %h%(!.%F{red}%B#%b%f.>) ' RPS1='%B%(0?..%F{red})%?%(0?..%f)%b %* %j' ;; esac # # Pretty-print return status # precmd_prettyreturn() { # Update psvar[1] to reflect pretty-printed output status local pretty=$result if (( pretty > 128 )); then pretty=${signals[$(( pretty - 128 + 1 ))]} fi psvar[1]=$pretty } add-zsh-hook precmd precmd_prettyreturn # Replace return status in prompts to use pretty-printing PS1=${PS1//\%\?/\%v} RPS1=${RPS1//\%\?/\%v} # # Check for kerberos tickets, and silently renew them when possible. # This could probably be done directly in the prompt string. # zmodload zsh/datetime typeset -gi _last_kinit_R=0 precmd_kerberos() { if whence klist >&/dev/null && klist -s >&/dev/null; then HAVE_KRBTICKETS=1 # Attempt silent renewal at most once every 10 minutes. kinit -R # is a no-op when the ticket isn't renewable, so this is safe. if (( EPOCHSECONDS - _last_kinit_R > 600 )); then _last_kinit_R=$EPOCHSECONDS kinit -R 2>/dev/null &! fi else HAVE_KRBTICKETS= fi } add-zsh-hook precmd precmd_kerberos # # Show current VCS branch / revision in the prompt (vcs_info) # autoload -Uz vcs_info zstyle ':vcs_info:*' enable git svn hg zstyle ':vcs_info:*' check-for-changes true zstyle ':vcs_info:*' stagedstr '+' zstyle ':vcs_info:*' unstagedstr '*' zstyle ':vcs_info:git:*' formats ' (%F{cyan}%b%f%u%c)' zstyle ':vcs_info:git:*' actionformats ' (%F{cyan}%b%f|%F{red}%a%f%u%c)' # For SVN, only show a branch label when we're actually on a /branches/foo # checkout. On trunk or non-trunk-layout repos, show only the revision. # (See vcs_info hooks in zshcontrib(1).) zstyle ':vcs_info:svn:*' formats ' (%F{magenta}%b%f%u%c)' zstyle ':vcs_info:svn:*' actionformats ' (%F{magenta}%b%f|%F{red}%a%f%u%c)' zstyle ':vcs_info:svn+set-branch-format:*' hooks svn-cond-branch +vi-svn-cond-branch() { if [[ $hook_com[branch] == (branches|tags)/* ]]; then hook_com[branch-replace]="${hook_com[branch]}:r${hook_com[revision]}" else hook_com[branch-replace]="r${hook_com[revision]}" fi ret=1 } zstyle ':vcs_info:hg:*' formats ' (%F{yellow}%b%f)' add-zsh-hook precmd vcs_info # # Set screen window titles intelligently # # Reset the screen window title before the prompt is displayed. precmd_screentitle() { screentitle ${SHELL:t} } add-zsh-hook precmd precmd_screentitle # Parse the command line in order to find out what the # about-to-be-executed command is, and set it as the screen window # title. preexec_screentitle() { # Set the title to the current command, intelligently setopt local_options extended_glob typeset -a line line=(${(z)2}) local cmd foo if [[ $line[1] == fg || $line[1] == %* ]]; then local jobid=${line[(r)%*]} if [[ -z $jobid ]]; then jobid=%+; fi jobs $jobid | read jobid foo line=(${(z)${(e):-\$jobtexts$jobid}}) fi cmd=${line[(r)^(*=*|sudo)]:t} screentitle $cmd } add-zsh-hook preexec preexec_screentitle # Use magic escape sequences to set the screen window title. screentitle() { if [[ $TERM == (screen*|tmux*) ]]; then print -n -- "\ek$1\e\\" elif [[ $TERM == (*xterm*|rxvt|(dt|k|E)term) ]]; then print -n -- "\e]0;$1\a" fi } # # Directory listing # zmodload zsh/complist LISTPROMPT= # Use list-style completion setupls() { local cachefile=${ZDOTDIR:-$HOME}/.zsh-ls-cache local dircolorsfile # See if we have a ~/.dircolors file. if [[ -r $HOME/.dircolors ]]; then dircolorsfile="$HOME/.dircolors" else dircolorsfile="" fi # Use cached result if it exists and is less than 24 hours old if [[ -r $cachefile ]] && [[ -n $cachefile(#qN.mh-24) ]]; then source $cachefile return fi local ls lsver foundls dircolors dcver # The complexity of this is necessary because some systems (*cough* # Athena) have ancient versions of gls floating around for ls in `whence -ap gls ls`; do # The / is for BSD ls, which ignores --version (in fact, there # is no way to ask its version), to lock in on a directory I # know is local and small lsver="`command $ls --version / 2> /dev/null`" if [[ $lsver == *Stallman* ]]; then foundls=1 lsver=${${=lsver}[3]} break fi done ls="command $ls" # Just to be sure if (( foundls )); then # Find a dircolors that matches the version of ls for dircolors in `whence -p gdircolors dircolors`; do dcver="`command $dircolors --version`" if [[ $lsver == ${${=dcver}[3]} ]]; then # We have a winner eval `command $dircolors -b $dircolorsfile` ZLS_COLORS="$LS_COLORS" unset LSCOLORS ls="$ls --color=auto" break fi done else # Didn't find a satisfactory ls, fall back to what is # hopefully BSD ls. This isn't quite right, because this # could also be an old version of GNU ls. ls="ls -G" fi # Show symbols after file names ls="$ls -F" alias ls="$ls" # Cache the result for future shells { print "alias ls=${(qq)ls}" [[ -n $LS_COLORS ]] && print "export LS_COLORS=${(qq)LS_COLORS}" [[ -n $ZLS_COLORS ]] && print "export ZLS_COLORS=${(qq)ZLS_COLORS}" } > $cachefile } setupls; unfunction setupls truncatedls() { # Print at most 10 lines of ls output # Note that this depends on the ls alias above for default formatting local -a lines lines=("${(@f)$(ls -C "$@")}") if (( ${#lines} > 10 )); then print -l -- $lines[1,9] ".. $((${#lines} - 9)) more lines .." else print -l -- $lines fi } # # Change directory helper # chpwd_truncatedls() { # List the new directory on change [[ -t 1 ]] || return # require stdout to be a terminal truncatedls } add-zsh-hook chpwd chpwd_truncatedls # # Options # setopt no_hup # Don't HUP background processes on exit setopt csh_null_glob # Be happy if at least one glob expands setopt no_beep # Be quiet setopt correct # Enable command spelling correction setopt extended_glob # Allow negation in globs setopt interactive_comments # Allow # comments on command line setopt long_list_jobs # Show PID in job notifications # # History # HISTSIZE=50000 # History lines to store in memory SAVEHIST=50000 # History lines to save to disk HISTFILE=~/.history # File to save history to setopt append_history # Append instead of replacing history setopt share_history # Sync history across concurrent shells setopt extended_history # Keep timestamps on history entries setopt hist_ignore_dups # Remove repeated commands from history setopt hist_ignore_space # Space-prefixed commands stay out of history setopt hist_expire_dups_first # Drop duplicates first when history is full # # General environment # #export LESS="-M -h10 -g -P?f%f:(pipe). ?e[EOF].%t ?pb(%pb\%).$ -PmFile\:?f%f:(pipe). ?e[EOF].%t ?m[%i/%m]. Line\:%lt/%L ?pb(%pb\%).$ -PMFile\:?f%f:(pipe). ?e[EOF].%t ?m[%i/%m?x\:%x.]. Line\:%lt/%L Byte\:%bt/%B. ?pb(%pb\%)." export PAGER=less if [[ -z $SSH_CLIENT ]]; then # If a local connection, use emacs server if available, or vi if # not. if whence -p ec &> /dev/null; then export VISUAL=ec else export VISUAL=vi fi else # If connected via ssh, just use vi export VISUAL=vi fi export EDITOR=$VISUAL # # Aliases for connecting to other systems # alias cs='ssh -o "CheckHostIP no" -Y -t -p 2222 dan@clamshell.ambulatoryclam.net' # Use /usr/bin/ssh because /sw/bin/ssh has kerberos issues. alias nk='/usr/bin/ssh -X drkp@x.dialup.mit.edu' alias netmug='ssh ambclams@netmug.org' alias gi='ssh -Y -t drkp@giraffe.cs.washington.edu' alias al='ssh -Y -t drkp@alpaca.cs.washington.edu' alias ge='ssh -Y -t dan@geoduck.ambulatoryclam.net' alias or='ssh -Y -t dan@oryx.ambulatoryclam.net' # Other aliases alias ks='killall ssh' alias cl='clear && reset' # For cleaning up after interrupted # ssh sessions make a mess of things. alias qq='setxkbmap' # For when vmware screws up modifiers alias ll='ls -alF' alias la='ls -A' alias dfh='df -h' alias duh='du -csh * | sort -h' alias ipify='curl api.ipify.org' alias ipify4='curl -4 api4.ipify.org' alias ipify6='curl -6 api6.ipify.org' # This got a lot more complicated because of systemd... syslog() { # Prefer journald if available if command -v journalctl >/dev/null 2>&1 && [[ -d /run/systemd/journal ]]; then # Try without sudo first if journalctl -n 1 --no-pager >/dev/null 2>&1 /dev/null 2>&1 && [[ -d /run/systemd/journal ]]; then # Producer: follow + some history, no internal pager local jc_base=(journalctl -f -n 500 --no-pager) # Check if we need sudo if journalctl -n 1 --no-pager >/dev/null 2>&1 tail -F piped to less local file for file in /var/log/system.log /var/log/messages /var/log/syslog; do if [[ -e $file ]]; then if [[ -r $file ]]; then tail -n 500 -F "$file" | less else sudo tail -n 500 -F "$file" | less fi return fi done print -u2 "syslogf: no journal and no /var/log/messages or /var/log/syslog" return 1 } alias maillog='sudo less /var/log/mail.log' # # Remove resource limits # unlimit 2>/dev/null # # Keybindings # zle -N parent-dir parent-dir() { local cursor=$CURSOR # Save cursor position local buffer=$BUFFER # Save buffer contents BUFFER= # Clear edit buffer zle -R # Redraw the now empty input line local i cmd=.. # Consume numeric argument for (( i = 1 ; i < NUMERIC ; i++ )); do cmd=$cmd/.. done print "cd $cmd" # So it's clear what's happening from scrollback cd $cmd zle reset-prompt # Redraw the prompt itself BUFFER=$buffer # Restore the buffer contents CURSOR=$cursor # And move the cursor back to where it was } bindkey '\ej' parent-dir # C-x C-e: edit current command line in $VISUAL/$EDITOR (zsh's # autoloadable edit-command-line). Saves you when a one-liner has grown # to the point that a real editor would help. autoload -Uz edit-command-line zle -N edit-command-line bindkey '^X^E' edit-command-line # # Help (bound to M-h) # (( ${+aliases[run-help]} )) && unalias run-help autoload -Uz run-help autoload -Uz run-help-sudo run-help-svn run-help-p4 run-help-git # # Load Debian/Ubuntu's command_not_found script. This adds a # precmd/preexec hook to print the package names for unavailable # commands. # if [[ -r /etc/zsh_command_not_found ]]; then source /etc/zsh_command_not_found fi # # Completion stuff # # Enable caching for completion zstyle ':completion:*' use-cache on zstyle ':completion:*' cache-path ~/.zsh-cache # Find known hosts and autocomplete them when appropriate. This # requires first finding a GNU sed in order to remove duplicates. In # this context, duplicates means any hostname that's a prefix of # another hostname, e.g. "theremin" and "theremin.csail" are # duplicates of "theremin.csail.mit.edu" load_known_hosts() { [[ -r ~/.ssh/known_hosts || -r /etc/ssh/ssh_known_hosts ]] || return local gsed gsedversion local -a myhosts integer foundgsed=0 for gsed in $(whence -ap gsed sed); do gsedversion=$($gsed --version 2>&1) if [[ $gsedversion == *GNU* ]]; then foundgsed=1 break fi done if (( foundgsed )); then myhosts=(${=$(cat ~/.ssh/known_hosts /etc/ssh/ssh_known_hosts 2>/dev/null | cut -f 1 -d " " | tr , '\n' | sort | uniq | grep -v 'ssh.*key$' | $gsed -r -e "H;x;/^(.*)\n\1\./{\${x;p};d};s/\n.*$//;1!p;\${x;p};d")}) else # Fall back on not removing duplicates myhosts=(${=$(cat ~/.ssh/known_hosts /etc/ssh/ssh_known_hosts 2>/dev/null | cut -f 1 -d " " | tr , '\n' | sort | uniq | grep -v 'ssh.*key$')}) fi zstyle ':completion:*' hosts $myhosts } load_known_hosts unfunction load_known_hosts # Always display the process info when killing, and use the menu zstyle ':completion:*:kill:*' force-list always zstyle ':completion:*:kill:*' menu yes select zstyle ':completion:*:processes' command 'ps -xww' zstyle ':completion:*:processes-names' command 'ps -xc -o command | uniq' # Don't rm/diff/kill the same file twice on the same line zstyle ':completion:*:(rm|kill|diff):*' ignore-line yes # Don't offer CVS directories as completions zstyle ':completion:*:(all-|)files' ignored-patterns '(|*/)CVS' zstyle ':completion:*:cd:*' ignored-patterns '(*/)#CVS' # Don't treat completion functions as commands zstyle ':completion:*:functions' ignored-patterns '_*' # Subversion repository locations zstyle ':completion:*:svn*:*' urls \ 'https://www.ambulatoryclam.net/svn/' \ 'http://www.ambulatoryclam.net/svn/' # Use a menu for selection on the second tab zstyle ':completion:*:*:*:*' menu select # The following lines were added by compinstall zstyle ':completion:*' auto-description 'specify: %d' zstyle ':completion:*' completer _expand _complete _correct _approximate zstyle ':completion:*' format 'Completing %d' zstyle ':completion:*' group-name '' zstyle ':completion:*' insert-unambiguous true zstyle ':completion:*' list-colors ${(s.:.)LS_COLORS} zstyle ':completion:*' matcher-list '' 'm:{a-zA-Z}={A-Za-z}' 'm:{a-z}={A-Z}' 'r:|[._-]=* r:|=*' zstyle ':completion:*' max-errors 2 numeric zstyle ':completion:*' original true zstyle ':completion:*' preserve-prefix '//[^/]##/' zstyle ':completion:*' verbose true zstyle ':completion:*' rehash true # Auto-detect new executables in $PATH # zsh-completions: must be added to fpath before compinit if [[ -d ~/.zsh/zsh-completions/src ]]; then fpath=(~/.zsh/zsh-completions/src $fpath) fi autoload -Uz compinit # Use the cached dump (compinit -C) only when it exists and is less than # 24h old; otherwise run a full compinit (which also re-runs the security # check on the dump). if [[ -n ~/.zcompdump(#qNmh-24) ]]; then compinit -C else compinit fi # End of lines added by compinstall # # Plugins # # Clone if missing: # git clone https://github.com/zsh-users/zsh-autosuggestions ~/.zsh/zsh-autosuggestions # git clone https://github.com/zsh-users/zsh-syntax-highlighting ~/.zsh/zsh-syntax-highlighting # git clone https://github.com/zsh-users/zsh-completions ~/.zsh/zsh-completions # git clone https://github.com/Aloxaf/fzf-tab ~/.zsh/fzf-tab # # fzf-tab: replaces zsh's completion menu with an fzf picker. Must be # loaded AFTER compinit but BEFORE any plugin that wraps widgets # (zsh-autosuggestions, zsh-syntax-highlighting). if [[ -r ~/.zsh/fzf-tab/fzf-tab.plugin.zsh ]] && (( $+commands[fzf] )); then source ~/.zsh/fzf-tab/fzf-tab.plugin.zsh # Use tmux popup for completion UI when inside tmux (1=on, popup pct). zstyle ':fzf-tab:*' fzf-command fzf zstyle ':fzf-tab:*' use-fzf-default-opts yes # Preview directories when completing cd / ls / etc. zstyle ':fzf-tab:complete:cd:*' fzf-preview 'ls -F --color=always $realpath 2>/dev/null || ls -F $realpath' # Preview process info when completing kill. zstyle ':fzf-tab:complete:(kill|ps):argument-rest' fzf-preview \ '[[ $group == "[process ID]" ]] && ps -p $word -o pid,user,command 2>/dev/null' fi if [[ -r ~/.zsh/zsh-autosuggestions/zsh-autosuggestions.zsh ]]; then source ~/.zsh/zsh-autosuggestions/zsh-autosuggestions.zsh fi # fzf: fuzzy finder. Provides Ctrl-R (history), Ctrl-T (file insert), # Alt-C (cd). Only activates if fzf is installed on this system. if (( $+commands[fzf] )); then # Prefer the modern `fzf --zsh` integration (fzf >= 0.48). Fall back # to sourcing the per-distro shell snippets for older versions. if fzf --zsh >/dev/null 2>&1; then source <(fzf --zsh) else for d in \ /opt/local/share/fzf/shell \ /usr/share/doc/fzf/examples \ /usr/share/fzf do if [[ -r $d/key-bindings.zsh ]]; then source $d/key-bindings.zsh [[ -r $d/completion.zsh ]] && source $d/completion.zsh break fi done unset d fi # If fd is available, use it for file/dir listing (faster than find, # honors .gitignore by default). if (( $+commands[fd] )); then export FZF_DEFAULT_COMMAND='fd --type f --hidden --follow --exclude .git' export FZF_CTRL_T_COMMAND="$FZF_DEFAULT_COMMAND" export FZF_ALT_C_COMMAND='fd --type d --hidden --follow --exclude .git' fi # If bat is available, use it for previews. if (( $+commands[bat] )); then export FZF_CTRL_T_OPTS="--preview 'bat -n --color=always --line-range :500 {}'" fi fi # syntax-highlighting must be sourced last if [[ -r ~/.zsh/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh ]]; then source ~/.zsh/zsh-syntax-highlighting/zsh-syntax-highlighting.zsh fi # Added by LM Studio CLI (lms) export PATH="$PATH:/Users/dan/.lmstudio/bin" # End of LM Studio CLI section